diff options
Diffstat (limited to 'ICSharpCode.Decompiler/ILAst')
18 files changed, 10161 insertions, 0 deletions
diff --git a/ICSharpCode.Decompiler/ILAst/AsyncDecompiler.cs b/ICSharpCode.Decompiler/ILAst/AsyncDecompiler.cs new file mode 100644 index 00000000..801673f9 --- /dev/null +++ b/ICSharpCode.Decompiler/ILAst/AsyncDecompiler.cs @@ -0,0 +1,704 @@ +// 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 + } +} diff --git a/ICSharpCode.Decompiler/ILAst/DefaultDictionary.cs b/ICSharpCode.Decompiler/ILAst/DefaultDictionary.cs new file mode 100644 index 00000000..1e359a3d --- /dev/null +++ b/ICSharpCode.Decompiler/ILAst/DefaultDictionary.cs @@ -0,0 +1,128 @@ +// 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; +using System.Collections.Generic; + +namespace ICSharpCode.Decompiler.ILAst +{ + /// <summary> + /// Dictionary with default values. + /// </summary> + internal sealed class DefaultDictionary<TKey, TValue> : IDictionary<TKey, TValue> + { + readonly IDictionary<TKey, TValue> dict; + readonly Func<TKey, TValue> defaultProvider; + + public DefaultDictionary(TValue defaultValue, IDictionary<TKey, TValue> dictionary = null) + : this(key => defaultValue, dictionary) + { + } + + public DefaultDictionary(Func<TKey, TValue> defaultProvider = null, IDictionary<TKey, TValue> dictionary = null) + { + dict = dictionary ?? new Dictionary<TKey, TValue>(); + this.defaultProvider = defaultProvider ?? (key => default(TValue)); + } + + public TValue this[TKey key] { + get { + TValue val; + if (dict.TryGetValue(key, out val)) + return val; + else + return dict[key] = defaultProvider(key); + } + set { + dict[key] = value; + } + } + + public ICollection<TKey> Keys { + get { return dict.Keys; } + } + + public ICollection<TValue> Values { + get { return dict.Values; } + } + + public int Count { + get { return dict.Count; } + } + + bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly { + get { return false; } + } + + public bool ContainsKey(TKey key) + { + return dict.ContainsKey(key); + } + + public void Add(TKey key, TValue value) + { + dict.Add(key, value); + } + + public bool Remove(TKey key) + { + return dict.Remove(key); + } + + public bool TryGetValue(TKey key, out TValue value) + { + return dict.TryGetValue(key, out value); + } + + void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) + { + dict.Add(item); + } + + public void Clear() + { + dict.Clear(); + } + + bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) + { + return dict.Contains(item); + } + + void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) + { + dict.CopyTo(array, arrayIndex); + } + + bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) + { + return dict.Remove(item); + } + + IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() + { + return dict.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return dict.GetEnumerator(); + } + } +}
\ No newline at end of file diff --git a/ICSharpCode.Decompiler/ILAst/GotoRemoval.cs b/ICSharpCode.Decompiler/ILAst/GotoRemoval.cs new file mode 100644 index 00000000..fafe13e1 --- /dev/null +++ b/ICSharpCode.Decompiler/ILAst/GotoRemoval.cs @@ -0,0 +1,319 @@ +// 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.Diagnostics; +using System.IO; +using System.Collections.Generic; +using System.Linq; + +namespace ICSharpCode.Decompiler.ILAst +{ + public class GotoRemoval + { + Dictionary<ILNode, ILNode> parent = new Dictionary<ILNode, ILNode>(); + Dictionary<ILNode, ILNode> nextSibling = new Dictionary<ILNode, ILNode>(); + + public void RemoveGotos(ILBlock method) + { + // Build the navigation data + parent[method] = null; + foreach (ILNode node in method.GetSelfAndChildrenRecursive<ILNode>()) { + ILNode previousChild = null; + foreach (ILNode child in node.GetChildren()) { + if (parent.ContainsKey(child)) + throw new Exception("The following expression is linked from several locations: " + child.ToString()); + parent[child] = node; + if (previousChild != null) + nextSibling[previousChild] = child; + previousChild = child; + } + if (previousChild != null) + nextSibling[previousChild] = null; + } + + // Simplify gotos + bool modified; + do { + modified = false; + foreach (ILExpression gotoExpr in method.GetSelfAndChildrenRecursive<ILExpression>(e => e.Code == ILCode.Br || e.Code == ILCode.Leave)) { + modified |= TrySimplifyGoto(gotoExpr); + } + } while(modified); + + RemoveRedundantCode(method); + } + + public static void RemoveRedundantCode(ILBlock method) + { + // Remove dead lables and nops + HashSet<ILLabel> liveLabels = new HashSet<ILLabel>(method.GetSelfAndChildrenRecursive<ILExpression>(e => e.IsBranch()).SelectMany(e => e.GetBranchTargets())); + foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) { + block.Body = block.Body.Where(n => !n.Match(ILCode.Nop) && !(n is ILLabel && !liveLabels.Contains((ILLabel)n))).ToList(); + } + + // Remove redundant continue + foreach(ILWhileLoop loop in method.GetSelfAndChildrenRecursive<ILWhileLoop>()) { + var body = loop.BodyBlock.Body; + if (body.Count > 0 && body.Last().Match(ILCode.LoopContinue)) { + body.RemoveAt(body.Count - 1); + } + } + + // Remove redundant break at the end of case + // Remove redundant case blocks altogether + foreach(ILSwitch ilSwitch in method.GetSelfAndChildrenRecursive<ILSwitch>()) { + foreach(ILBlock ilCase in ilSwitch.CaseBlocks) { + Debug.Assert(ilCase.EntryGoto == null); + + int count = ilCase.Body.Count; + if (count >= 2) { + if (ilCase.Body[count - 2].IsUnconditionalControlFlow() && + ilCase.Body[count - 1].Match(ILCode.LoopOrSwitchBreak)) + { + ilCase.Body.RemoveAt(count - 1); + } + } + } + + var defaultCase = ilSwitch.CaseBlocks.SingleOrDefault(cb => cb.Values == null); + // If there is no default block, remove empty case blocks + if (defaultCase == null || (defaultCase.Body.Count == 1 && defaultCase.Body.Single().Match(ILCode.LoopOrSwitchBreak))) { + ilSwitch.CaseBlocks.RemoveAll(b => b.Body.Count == 1 && b.Body.Single().Match(ILCode.LoopOrSwitchBreak)); + } + } + + // Remove redundant return at the end of method + if (method.Body.Count > 0 && method.Body.Last().Match(ILCode.Ret) && ((ILExpression)method.Body.Last()).Arguments.Count == 0) { + method.Body.RemoveAt(method.Body.Count - 1); + } + + // Remove unreachable return statements + bool modified = false; + foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) { + for (int i = 0; i < block.Body.Count - 1;) { + if (block.Body[i].IsUnconditionalControlFlow() && block.Body[i+1].Match(ILCode.Ret)) { + modified = true; + block.Body.RemoveAt(i+1); + } else { + i++; + } + } + } + if (modified) { + // More removals might be possible + new GotoRemoval().RemoveGotos(method); + } + } + + IEnumerable<ILNode> GetParents(ILNode node) + { + ILNode current = node; + while(true) { + current = parent[current]; + if (current == null) + yield break; + yield return current; + } + } + + bool TrySimplifyGoto(ILExpression gotoExpr) + { + Debug.Assert(gotoExpr.Code == ILCode.Br || gotoExpr.Code == ILCode.Leave); + Debug.Assert(gotoExpr.Prefixes == null); + Debug.Assert(gotoExpr.Operand != null); + + ILNode target = Enter(gotoExpr, new HashSet<ILNode>()); + if (target == null) + return false; + + // The gotoExper is marked as visited because we do not want to + // walk over node which we plan to modify + + // The simulated path always has to start in the same try-block + // in other for the same finally blocks to be executed. + + if (target == Exit(gotoExpr, new HashSet<ILNode>() { gotoExpr })) { + gotoExpr.Code = ILCode.Nop; + gotoExpr.Operand = null; + if (target is ILExpression) + ((ILExpression)target).ILRanges.AddRange(gotoExpr.ILRanges); + gotoExpr.ILRanges.Clear(); + return true; + } + + ILNode breakBlock = GetParents(gotoExpr).FirstOrDefault(n => n is ILWhileLoop || n is ILSwitch); + if (breakBlock != null && target == Exit(breakBlock, new HashSet<ILNode>() { gotoExpr })) { + gotoExpr.Code = ILCode.LoopOrSwitchBreak; + gotoExpr.Operand = null; + return true; + } + + ILNode continueBlock = GetParents(gotoExpr).FirstOrDefault(n => n is ILWhileLoop); + if (continueBlock != null && target == Enter(continueBlock, new HashSet<ILNode>() { gotoExpr })) { + gotoExpr.Code = ILCode.LoopContinue; + gotoExpr.Operand = null; + return true; + } + + return false; + } + + /// <summary> + /// Get the first expression to be excecuted if the instruction pointer is at the start of the given node. + /// Try blocks may not be entered in any way. If possible, the try block is returned as the node to be executed. + /// </summary> + ILNode Enter(ILNode node, HashSet<ILNode> visitedNodes) + { + if (node == null) + throw new ArgumentNullException(); + + if (!visitedNodes.Add(node)) + return null; // Infinite loop + + ILLabel label = node as ILLabel; + if (label != null) { + return Exit(label, visitedNodes); + } + + ILExpression expr = node as ILExpression; + if (expr != null) { + if (expr.Code == ILCode.Br || expr.Code == ILCode.Leave) { + ILLabel target = (ILLabel)expr.Operand; + // Early exit - same try-block + if (GetParents(expr).OfType<ILTryCatchBlock>().FirstOrDefault() == GetParents(target).OfType<ILTryCatchBlock>().FirstOrDefault()) + return Enter(target, visitedNodes); + // Make sure we are not entering any try-block + var srcTryBlocks = GetParents(expr).OfType<ILTryCatchBlock>().Reverse().ToList(); + var dstTryBlocks = GetParents(target).OfType<ILTryCatchBlock>().Reverse().ToList(); + // Skip blocks that we are already in + int i = 0; + while(i < srcTryBlocks.Count && i < dstTryBlocks.Count && srcTryBlocks[i] == dstTryBlocks[i]) i++; + if (i == dstTryBlocks.Count) { + return Enter(target, visitedNodes); + } else { + ILTryCatchBlock dstTryBlock = dstTryBlocks[i]; + // Check that the goto points to the start + ILTryCatchBlock current = dstTryBlock; + while(current != null) { + foreach(ILNode n in current.TryBlock.Body) { + if (n is ILLabel) { + if (n == target) + return dstTryBlock; + } else if (!n.Match(ILCode.Nop)) { + current = n as ILTryCatchBlock; + break; + } + } + } + return null; + } + } else if (expr.Code == ILCode.Nop) { + return Exit(expr, visitedNodes); + } else if (expr.Code == ILCode.LoopOrSwitchBreak) { + ILNode breakBlock = GetParents(expr).First(n => n is ILWhileLoop || n is ILSwitch); + return Exit(breakBlock, new HashSet<ILNode>() { expr }); + } else if (expr.Code == ILCode.LoopContinue) { + ILNode continueBlock = GetParents(expr).First(n => n is ILWhileLoop); + return Enter(continueBlock, new HashSet<ILNode>() { expr }); + } else { + return expr; + } + } + + ILBlock block = node as ILBlock; + if (block != null) { + if (block.EntryGoto != null) { + return Enter(block.EntryGoto, visitedNodes); + } else if (block.Body.Count > 0) { + return Enter(block.Body[0], visitedNodes); + } else { + return Exit(block, visitedNodes); + } + } + + ILCondition cond = node as ILCondition; + if (cond != null) { + return cond.Condition; + } + + ILWhileLoop loop = node as ILWhileLoop; + if (loop != null) { + if (loop.Condition != null) { + return loop.Condition; + } else { + return Enter(loop.BodyBlock, visitedNodes); + } + } + + ILTryCatchBlock tryCatch = node as ILTryCatchBlock; + if (tryCatch != null) { + return tryCatch; + } + + ILSwitch ilSwitch = node as ILSwitch; + if (ilSwitch != null) { + return ilSwitch.Condition; + } + + throw new NotSupportedException(node.GetType().ToString()); + } + + /// <summary> + /// Get the first expression to be excecuted if the instruction pointer is at the end of the given node + /// </summary> + ILNode Exit(ILNode node, HashSet<ILNode> visitedNodes) + { + if (node == null) + throw new ArgumentNullException(); + + ILNode nodeParent = parent[node]; + if (nodeParent == null) + return null; // Exited main body + + if (nodeParent is ILBlock) { + ILNode nextNode = nextSibling[node]; + if (nextNode != null) { + return Enter(nextNode, visitedNodes); + } else { + return Exit(nodeParent, visitedNodes); + } + } + + if (nodeParent is ILCondition) { + return Exit(nodeParent, visitedNodes); + } + + if (nodeParent is ILTryCatchBlock) { + // Finally blocks are completely ignored. + // We rely on the fact that try blocks can not be entered. + return Exit(nodeParent, visitedNodes); + } + + if (nodeParent is ILSwitch) { + return null; // Implicit exit from switch is not allowed + } + + if (nodeParent is ILWhileLoop) { + return Enter(nodeParent, visitedNodes); + } + + throw new NotSupportedException(nodeParent.GetType().ToString()); + } + } +} diff --git a/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs b/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs new file mode 100644 index 00000000..21b2b150 --- /dev/null +++ b/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs @@ -0,0 +1,839 @@ +// 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.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Mono.Cecil; +using Mono.Cecil.Cil; +using Cecil = Mono.Cecil; + +namespace ICSharpCode.Decompiler.ILAst +{ + /// <summary> + /// Converts stack-based bytecode to variable-based bytecode by calculating use-define chains + /// </summary> + public class ILAstBuilder + { + /// <summary> Immutable </summary> + struct StackSlot + { + public readonly ByteCode[] Definitions; // Reaching definitions of this stack slot + public readonly ILVariable LoadFrom; // Variable used for storage of the value + + public StackSlot(ByteCode[] definitions, ILVariable loadFrom) + { + Definitions = definitions; + LoadFrom = loadFrom; + } + + public static StackSlot[] ModifyStack(StackSlot[] stack, int popCount, int pushCount, ByteCode pushDefinition) + { + StackSlot[] newStack = new StackSlot[stack.Length - popCount + pushCount]; + Array.Copy(stack, newStack, stack.Length - popCount); + for (int i = stack.Length - popCount; i < newStack.Length; i++) { + newStack[i] = new StackSlot(new [] { pushDefinition }, null); + } + return newStack; + } + } + + /// <summary> Immutable </summary> + struct VariableSlot + { + public readonly ByteCode[] Definitions; // Reaching deinitions of this variable + public readonly bool UnknownDefinition; // Used for initial state and exceptional control flow + + static readonly VariableSlot UnknownInstance = new VariableSlot(new ByteCode[0], true); + + public VariableSlot(ByteCode[] definitions, bool unknownDefinition) + { + Definitions = definitions; + UnknownDefinition = unknownDefinition; + } + + public static VariableSlot[] CloneVariableState(VariableSlot[] state) + { + VariableSlot[] clone = new VariableSlot[state.Length]; + Array.Copy(state, clone, state.Length); + return clone; + } + + public static VariableSlot[] MakeUknownState(int varCount) + { + VariableSlot[] unknownVariableState = new VariableSlot[varCount]; + for (int i = 0; i < unknownVariableState.Length; i++) { + unknownVariableState[i] = UnknownInstance; + } + return unknownVariableState; + } + } + + sealed class ByteCode + { + public ILLabel Label; // Non-null only if needed + public int Offset; + public int EndOffset; + public ILCode Code; + public object Operand; + public int? PopCount; // Null means pop all + public int PushCount; + public string Name { get { return "IL_" + Offset.ToString("X2"); } } + public ByteCode Next; + public Instruction[] Prefixes; // Non-null only if needed + public StackSlot[] StackBefore; // Unique per bytecode; not shared + public VariableSlot[] VariablesBefore; // Unique per bytecode; not shared + public List<ILVariable> StoreTo; // Store result of instruction to those AST variables + + public bool IsVariableDefinition { + get { + return (Code == ILCode.Stloc) || (Code == ILCode.Ldloca && Next != null && Next.Code == ILCode.Initobj); + } + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + + // Label + sb.Append(Name); + sb.Append(':'); + if (Label != null) + sb.Append('*'); + + // Name + sb.Append(' '); + if (Prefixes != null) { + foreach (var prefix in Prefixes) { + sb.Append(prefix.OpCode.Name); + sb.Append(' '); + } + } + sb.Append(Code.GetName()); + + if (Operand != null) { + sb.Append(' '); + if (Operand is Instruction) { + sb.Append("IL_" + ((Instruction)Operand).Offset.ToString("X2")); + } else if (Operand is Instruction[]) { + foreach(Instruction inst in (Instruction[])Operand) { + sb.Append("IL_" + inst.Offset.ToString("X2")); + sb.Append(" "); + } + } else if (Operand is ILLabel) { + sb.Append(((ILLabel)Operand).Name); + } else if (Operand is ILLabel[]) { + foreach(ILLabel label in (ILLabel[])Operand) { + sb.Append(label.Name); + sb.Append(" "); + } + } else { + sb.Append(Operand.ToString()); + } + } + + if (StackBefore != null) { + sb.Append(" StackBefore={"); + bool first = true; + foreach (StackSlot slot in StackBefore) { + if (!first) sb.Append(","); + bool first2 = true; + foreach(ByteCode defs in slot.Definitions) { + if (!first2) sb.Append("|"); + sb.AppendFormat("IL_{0:X2}", defs.Offset); + first2 = false; + } + first = false; + } + sb.Append("}"); + } + + if (StoreTo != null && StoreTo.Count > 0) { + sb.Append(" StoreTo={"); + bool first = true; + foreach (ILVariable stackVar in StoreTo) { + if (!first) sb.Append(","); + sb.Append(stackVar.Name); + first = false; + } + sb.Append("}"); + } + + if (VariablesBefore != null) { + sb.Append(" VarsBefore={"); + bool first = true; + foreach (VariableSlot varSlot in VariablesBefore) { + if (!first) sb.Append(","); + if (varSlot.UnknownDefinition) { + sb.Append("?"); + } else { + bool first2 = true; + foreach (ByteCode storedBy in varSlot.Definitions) { + if (!first2) sb.Append("|"); + sb.AppendFormat("IL_{0:X2}", storedBy.Offset); + first2 = false; + } + } + first = false; + } + sb.Append("}"); + } + + return sb.ToString(); + } + } + + MethodDefinition methodDef; + bool optimize; + + // Virtual instructions to load exception on stack + Dictionary<ExceptionHandler, ByteCode> ldexceptions = new Dictionary<ExceptionHandler, ByteCode>(); + + DecompilerContext context; + + public List<ILNode> Build(MethodDefinition methodDef, bool optimize, DecompilerContext context) + { + this.methodDef = methodDef; + this.optimize = optimize; + this.context = context; + + if (methodDef.Body.Instructions.Count == 0) return new List<ILNode>(); + + List<ByteCode> body = StackAnalysis(methodDef); + + List<ILNode> ast = ConvertToAst(body, new HashSet<ExceptionHandler>(methodDef.Body.ExceptionHandlers)); + + return ast; + } + + static int offsetIdx; + List<ByteCode> StackAnalysis(MethodDefinition methodDef) + { + Dictionary<Instruction, ByteCode> instrToByteCode = new Dictionary<Instruction, ByteCode>(); + + // Create temporary structure for the stack analysis + List<ByteCode> body = new List<ByteCode>(methodDef.Body.Instructions.Count); + List<Instruction> prefixes = null; + foreach(Instruction inst in methodDef.Body.Instructions) { + if (inst.OpCode.OpCodeType == OpCodeType.Prefix) { + if (prefixes == null) + prefixes = new List<Instruction>(1); + prefixes.Add(inst); + continue; + } + ILCode code = (ILCode)inst.OpCode.Code; + object operand = inst.Operand; + ILCodeUtil.ExpandMacro(ref code, ref operand, methodDef.Body); + ByteCode byteCode = new ByteCode() { + Offset = inst.Offset, + EndOffset = inst.Next != null ? inst.Next.Offset : methodDef.Body.CodeSize, + Code = code, + Operand = operand, + PopCount = inst.GetPopDelta(methodDef), + PushCount = inst.GetPushDelta() + }; + if (prefixes != null) { + instrToByteCode[prefixes[0]] = byteCode; + byteCode.Offset = prefixes[0].Offset; + byteCode.Prefixes = prefixes.ToArray(); + prefixes = null; + } else { + instrToByteCode[inst] = byteCode; + } + body.Add(byteCode); + } + for (int i = 0; i < body.Count - 1; i++) { + body[i].Next = body[i + 1]; + } + + Stack<ByteCode> agenda = new Stack<ByteCode>(); + + int varCount = methodDef.Body.Variables.Count; + + var exceptionHandlerStarts = new HashSet<ByteCode>(methodDef.Body.ExceptionHandlers.Select(eh => instrToByteCode[eh.HandlerStart])); + + // Add known states + if(methodDef.Body.HasExceptionHandlers) { + foreach(ExceptionHandler ex in methodDef.Body.ExceptionHandlers) { + ByteCode handlerStart = instrToByteCode[ex.HandlerStart]; + handlerStart.StackBefore = new StackSlot[0]; + handlerStart.VariablesBefore = VariableSlot.MakeUknownState(varCount); + if (ex.HandlerType == ExceptionHandlerType.Catch || ex.HandlerType == ExceptionHandlerType.Filter) { + // Catch and Filter handlers start with the exeption on the stack + ByteCode ldexception = new ByteCode() { + Code = ILCode.Ldexception, + Operand = ex.CatchType, + PopCount = 0, + PushCount = 1 + }; + ldexceptions[ex] = ldexception; + handlerStart.StackBefore = new StackSlot[] { new StackSlot(new [] { ldexception }, null) }; + } + agenda.Push(handlerStart); + + if (ex.HandlerType == ExceptionHandlerType.Filter) + { + ByteCode filterStart = instrToByteCode[ex.FilterStart]; + ByteCode ldexception = new ByteCode() { + Code = ILCode.Ldexception, + Operand = ex.CatchType, + PopCount = 0, + PushCount = 1 + }; + // TODO: ldexceptions[ex] = ldexception; + filterStart.StackBefore = new StackSlot[] { new StackSlot(new [] { ldexception }, null) }; + filterStart.VariablesBefore = VariableSlot.MakeUknownState(varCount); + agenda.Push(filterStart); + } + } + } + + body[0].StackBefore = new StackSlot[0]; + body[0].VariablesBefore = VariableSlot.MakeUknownState(varCount); + agenda.Push(body[0]); + + // Process agenda + while(agenda.Count > 0) { + ByteCode byteCode = agenda.Pop(); + + // Calculate new stack + StackSlot[] newStack = StackSlot.ModifyStack(byteCode.StackBefore, byteCode.PopCount ?? byteCode.StackBefore.Length, byteCode.PushCount, byteCode); + + // Calculate new variable state + VariableSlot[] newVariableState = VariableSlot.CloneVariableState(byteCode.VariablesBefore); + if (byteCode.IsVariableDefinition) { + newVariableState[((VariableReference)byteCode.Operand).Index] = new VariableSlot(new [] { byteCode }, false); + } + + // After the leave, finally block might have touched the variables + if (byteCode.Code == ILCode.Leave) { + newVariableState = VariableSlot.MakeUknownState(varCount); + } + + // Find all successors + List<ByteCode> branchTargets = new List<ByteCode>(); + if (!byteCode.Code.IsUnconditionalControlFlow()) { + if (exceptionHandlerStarts.Contains(byteCode.Next)) { + // Do not fall though down to exception handler + // It is invalid IL as per ECMA-335 §12.4.2.8.1, but some obfuscators produce it + } else { + branchTargets.Add(byteCode.Next); + } + } + if (byteCode.Operand is Instruction[]) { + foreach(Instruction inst in (Instruction[])byteCode.Operand) { + ByteCode target = instrToByteCode[inst]; + branchTargets.Add(target); + // The target of a branch must have label + if (target.Label == null) { + target.Label = new ILLabel() { Name = target.Name }; + } + } + } else if (byteCode.Operand is Instruction) { + ByteCode target = instrToByteCode[(Instruction)byteCode.Operand]; + branchTargets.Add(target); + // The target of a branch must have label + if (target.Label == null) { + target.Label = new ILLabel() { Name = target.Name }; + } + } + + // Apply the state to successors + foreach (ByteCode branchTarget in branchTargets) { + if (branchTarget.StackBefore == null && branchTarget.VariablesBefore == null) { + if (branchTargets.Count == 1) { + branchTarget.StackBefore = newStack; + branchTarget.VariablesBefore = newVariableState; + } else { + // Do not share data for several bytecodes + branchTarget.StackBefore = StackSlot.ModifyStack(newStack, 0, 0, null); + branchTarget.VariablesBefore = VariableSlot.CloneVariableState(newVariableState); + } + agenda.Push(branchTarget); + } else { + if (branchTarget.StackBefore.Length != newStack.Length) { + throw new Exception("Inconsistent stack size at " + byteCode.Name); + } + + // Be careful not to change our new data - it might be reused for several branch targets. + // In general, be careful that two bytecodes never share data structures. + + bool modified = false; + + // Merge stacks - modify the target + for (int i = 0; i < newStack.Length; i++) { + ByteCode[] oldDefs = branchTarget.StackBefore[i].Definitions; + ByteCode[] newDefs = oldDefs.Union(newStack[i].Definitions); + if (newDefs.Length > oldDefs.Length) { + branchTarget.StackBefore[i] = new StackSlot(newDefs, null); + modified = true; + } + } + + // Merge variables - modify the target + for (int i = 0; i < newVariableState.Length; i++) { + VariableSlot oldSlot = branchTarget.VariablesBefore[i]; + VariableSlot newSlot = newVariableState[i]; + if (!oldSlot.UnknownDefinition) { + if (newSlot.UnknownDefinition) { + branchTarget.VariablesBefore[i] = newSlot; + modified = true; + } else { + ByteCode[] oldDefs = oldSlot.Definitions; + ByteCode[] newDefs = oldDefs.Union(newSlot.Definitions); + if (newDefs.Length > oldDefs.Length) { + branchTarget.VariablesBefore[i] = new VariableSlot(newDefs, false); + modified = true; + } + } + } + } + + if (modified) { + agenda.Push(branchTarget); + } + } + } + } + + // Occasionally the compilers or obfuscators generate unreachable code (which might be intentionally invalid) + // I believe it is safe to just remove it + body.RemoveAll(b => b.StackBefore == null); + + // Generate temporary variables to replace stack + foreach(ByteCode byteCode in body) { + int argIdx = 0; + int popCount = byteCode.PopCount ?? byteCode.StackBefore.Length; + for (int i = byteCode.StackBefore.Length - popCount; i < byteCode.StackBefore.Length; i++) { + var offset = byteCode.Offset != 0 ? byteCode.Offset : offsetIdx++; + ILVariable tmpVar = new ILVariable() { Name = string.Format("arg_{0:X2}_{1}", offset, argIdx), IsGenerated = true }; + byteCode.StackBefore[i] = new StackSlot(byteCode.StackBefore[i].Definitions, tmpVar); + foreach(ByteCode pushedBy in byteCode.StackBefore[i].Definitions) { + if (pushedBy.StoreTo == null) { + pushedBy.StoreTo = new List<ILVariable>(1); + } + pushedBy.StoreTo.Add(tmpVar); + } + argIdx++; + } + } + + // Try to use single temporary variable insted of several if possilbe (especially useful for dup) + // This has to be done after all temporary variables are assigned so we know about all loads + foreach(ByteCode byteCode in body) { + if (byteCode.StoreTo != null && byteCode.StoreTo.Count > 1) { + var locVars = byteCode.StoreTo; + // For each of the variables, find the location where it is loaded - there should be preciesly one + var loadedBy = locVars.Select(locVar => body.SelectMany(bc => bc.StackBefore).Single(s => s.LoadFrom == locVar)).ToList(); + // We now know that all the variables have a single load, + // Let's make sure that they have also a single store - us + if (loadedBy.All(slot => slot.Definitions.Length == 1 && slot.Definitions[0] == byteCode)) { + // Great - we can reduce everything into single variable + var offset = byteCode.Offset != 0 ? byteCode.Offset : offsetIdx++; + ILVariable tmpVar = new ILVariable() { Name = string.Format("expr_{0:X2}", offset), IsGenerated = true }; + byteCode.StoreTo = new List<ILVariable>() { tmpVar }; + foreach(ByteCode bc in body) { + for (int i = 0; i < bc.StackBefore.Length; i++) { + // Is it one of the variable to be merged? + if (locVars.Contains(bc.StackBefore[i].LoadFrom)) { + // Replace with the new temp variable + bc.StackBefore[i] = new StackSlot(bc.StackBefore[i].Definitions, tmpVar); + } + } + } + } + } + } + + // Split and convert the normal local variables + ConvertLocalVariables(body); + + // Convert branch targets to labels + foreach(ByteCode byteCode in body) { + if (byteCode.Operand is Instruction[]) { + List<ILLabel> newOperand = new List<ILLabel>(); + foreach(Instruction target in (Instruction[])byteCode.Operand) { + newOperand.Add(instrToByteCode[target].Label); + } + byteCode.Operand = newOperand.ToArray(); + } else if (byteCode.Operand is Instruction) { + byteCode.Operand = instrToByteCode[(Instruction)byteCode.Operand].Label; + } + } + + // Convert parameters to ILVariables + ConvertParameters(body); + + return body; + } + + static bool IsDeterministicLdloca(ByteCode b) + { + var v = b.Operand; + b = b.Next; + if (b.Code == ILCode.Initobj) return true; + + // instance method calls on value types use the variable ref deterministically + int stack = 1; + while (true) { + if (b.PopCount == null) return false; + stack -= b.PopCount.GetValueOrDefault(); + if (stack == 0) break; + if (stack < 0) return false; + if (b.Code.IsConditionalControlFlow() || b.Code.IsUnconditionalControlFlow()) return false; + switch (b.Code) { + case ILCode.Ldloc: + case ILCode.Ldloca: + case ILCode.Stloc: + if (b.Operand == v) return false; + break; + } + stack += b.PushCount; + b = b.Next; + if (b == null) return false; + } + if (b.Code == ILCode.Ldfld || b.Code == ILCode.Stfld) + return true; + return (b.Code == ILCode.Call || b.Code == ILCode.Callvirt) && ((MethodReference)b.Operand).HasThis; + } + + sealed class VariableInfo + { + public ILVariable Variable; + public List<ByteCode> Defs; + public List<ByteCode> Uses; + } + + /// <summary> + /// If possible, separates local variables into several independent variables. + /// It should undo any compilers merging. + /// </summary> + void ConvertLocalVariables(List<ByteCode> body) + { + foreach(VariableDefinition varDef in methodDef.Body.Variables) { + + // Find all definitions and uses of this variable + var defs = body.Where(b => b.Operand == varDef && b.IsVariableDefinition).ToList(); + var uses = body.Where(b => b.Operand == varDef && !b.IsVariableDefinition).ToList(); + + List<VariableInfo> newVars; + + // If the variable is pinned, use single variable. + // If any of the uses is from unknown definition, use single variable + // If any of the uses is ldloca with a nondeterministic usage pattern, use single variable + if (!optimize || varDef.IsPinned || uses.Any(b => b.VariablesBefore[varDef.Index].UnknownDefinition || (b.Code == ILCode.Ldloca && !IsDeterministicLdloca(b)))) { + newVars = new List<VariableInfo>(1) { new VariableInfo() { + Variable = new ILVariable() { + Name = string.IsNullOrEmpty(varDef.Name) ? "var_" + varDef.Index : varDef.Name, + Type = varDef.IsPinned ? ((PinnedType)varDef.VariableType).ElementType : varDef.VariableType, + OriginalVariable = varDef + }, + Defs = defs, + Uses = uses + }}; + } else { + // Create a new variable for each definition + newVars = defs.Select(def => new VariableInfo() { + Variable = new ILVariable() { + Name = (string.IsNullOrEmpty(varDef.Name) ? "var_" + varDef.Index : varDef.Name) + "_" + def.Offset.ToString("X2"), + Type = varDef.VariableType, + OriginalVariable = varDef + }, + Defs = new List<ByteCode>() { def }, + Uses = new List<ByteCode>() + }).ToList(); + + // VB.NET uses the 'init' to allow use of uninitialized variables. + // We do not really care about them too much - if the original variable + // was uninitialized at that point it means that no store was called and + // thus all our new variables must be uninitialized as well. + // So it does not matter which one we load. + + // TODO: We should add explicit initialization so that C# code compiles. + // Remember to handle cases where one path inits the variable, but other does not. + + // Add loads to the data structure; merge variables if necessary + foreach(ByteCode use in uses) { + ByteCode[] useDefs = use.VariablesBefore[varDef.Index].Definitions; + if (useDefs.Length == 1) { + VariableInfo newVar = newVars.Single(v => v.Defs.Contains(useDefs[0])); + newVar.Uses.Add(use); + } else { + List<VariableInfo> mergeVars = newVars.Where(v => v.Defs.Intersect(useDefs).Any()).ToList(); + VariableInfo mergedVar = new VariableInfo() { + Variable = mergeVars[0].Variable, + Defs = mergeVars.SelectMany(v => v.Defs).ToList(), + Uses = mergeVars.SelectMany(v => v.Uses).ToList() + }; + mergedVar.Uses.Add(use); + newVars = newVars.Except(mergeVars).ToList(); + newVars.Add(mergedVar); + } + } + } + + // Set bytecode operands + foreach(VariableInfo newVar in newVars) { + foreach(ByteCode def in newVar.Defs) { + def.Operand = newVar.Variable; + } + foreach(ByteCode use in newVar.Uses) { + use.Operand = newVar.Variable; + } + } + } + } + + public List<ILVariable> Parameters = new List<ILVariable>(); + + void ConvertParameters(List<ByteCode> body) + { + ILVariable thisParameter = null; + if (methodDef.HasThis) { + TypeReference type = methodDef.DeclaringType; + thisParameter = new ILVariable(); + thisParameter.Type = type.IsValueType ? new ByReferenceType(type) : type; + thisParameter.Name = "this"; + thisParameter.OriginalParameter = methodDef.Body.ThisParameter; + } + foreach (ParameterDefinition p in methodDef.Parameters) { + Parameters.Add(new ILVariable { Type = p.ParameterType, Name = p.Name, OriginalParameter = p }); + } + if (Parameters.Count > 0 && (methodDef.IsSetter || methodDef.IsAddOn || methodDef.IsRemoveOn)) { + // last parameter must be 'value', so rename it + Parameters.Last().Name = "value"; + } + foreach (ByteCode byteCode in body) { + ParameterDefinition p; + switch (byteCode.Code) { + case ILCode.__Ldarg: + p = (ParameterDefinition)byteCode.Operand; + byteCode.Code = ILCode.Ldloc; + byteCode.Operand = p.Index < 0 ? thisParameter : Parameters[p.Index]; + break; + case ILCode.__Starg: + p = (ParameterDefinition)byteCode.Operand; + byteCode.Code = ILCode.Stloc; + byteCode.Operand = p.Index < 0 ? thisParameter : Parameters[p.Index]; + break; + case ILCode.__Ldarga: + p = (ParameterDefinition)byteCode.Operand; + byteCode.Code = ILCode.Ldloca; + byteCode.Operand = p.Index < 0 ? thisParameter : Parameters[p.Index]; + break; + } + } + if (thisParameter != null) + Parameters.Add(thisParameter); + } + + List<ILNode> ConvertToAst(List<ByteCode> body, HashSet<ExceptionHandler> ehs) + { + List<ILNode> ast = new List<ILNode>(); + + while (ehs.Any()) { + ILTryCatchBlock tryCatchBlock = new ILTryCatchBlock(); + + // Find the first and widest scope + int tryStart = ehs.Min(eh => eh.TryStart.Offset); + int tryEnd = ehs.Where(eh => eh.TryStart.Offset == tryStart).Max(eh => eh.TryEnd.Offset); + var handlers = ehs.Where(eh => eh.TryStart.Offset == tryStart && eh.TryEnd.Offset == tryEnd).ToList(); + + // Remember that any part of the body migt have been removed due to unreachability + + // Cut all instructions up to the try block + { + int tryStartIdx = 0; + while (tryStartIdx < body.Count && body[tryStartIdx].Offset < tryStart) tryStartIdx++; + ast.AddRange(ConvertToAst(body.CutRange(0, tryStartIdx))); + } + + // Cut the try block + { + HashSet<ExceptionHandler> nestedEHs = new HashSet<ExceptionHandler>(ehs.Where(eh => (tryStart <= eh.TryStart.Offset && eh.TryEnd.Offset < tryEnd) || (tryStart < eh.TryStart.Offset && eh.TryEnd.Offset <= tryEnd))); + ehs.ExceptWith(nestedEHs); + int tryEndIdx = 0; + while (tryEndIdx < body.Count && body[tryEndIdx].Offset < tryEnd) tryEndIdx++; + tryCatchBlock.TryBlock = new ILBlock(ConvertToAst(body.CutRange(0, tryEndIdx), nestedEHs)); + } + + // Cut all handlers + tryCatchBlock.CatchBlocks = new List<ILTryCatchBlock.CatchBlock>(); + foreach(ExceptionHandler eh in handlers) { + int handlerEndOffset = eh.HandlerEnd == null ? methodDef.Body.CodeSize : eh.HandlerEnd.Offset; + int startIdx = 0; + while (startIdx < body.Count && body[startIdx].Offset < eh.HandlerStart.Offset) startIdx++; + int endIdx = 0; + while (endIdx < body.Count && body[endIdx].Offset < handlerEndOffset) endIdx++; + HashSet<ExceptionHandler> nestedEHs = new HashSet<ExceptionHandler>(ehs.Where(e => (eh.HandlerStart.Offset <= e.TryStart.Offset && e.TryEnd.Offset < handlerEndOffset) || (eh.HandlerStart.Offset < e.TryStart.Offset && e.TryEnd.Offset <= handlerEndOffset))); + ehs.ExceptWith(nestedEHs); + List<ILNode> handlerAst = ConvertToAst(body.CutRange(startIdx, endIdx - startIdx), nestedEHs); + if (eh.HandlerType == ExceptionHandlerType.Catch) { + ILTryCatchBlock.CatchBlock catchBlock = new ILTryCatchBlock.CatchBlock() { + ExceptionType = eh.CatchType, + Body = handlerAst + }; + // Handle the automatically pushed exception on the stack + ByteCode ldexception = ldexceptions[eh]; + if (ldexception.StoreTo == null || ldexception.StoreTo.Count == 0) { + // Exception is not used + catchBlock.ExceptionVariable = null; + } else if (ldexception.StoreTo.Count == 1) { + ILExpression first = catchBlock.Body[0] as ILExpression; + if (first != null && + first.Code == ILCode.Pop && + first.Arguments[0].Code == ILCode.Ldloc && + first.Arguments[0].Operand == ldexception.StoreTo[0]) + { + // The exception is just poped - optimize it all away; + if (context.Settings.AlwaysGenerateExceptionVariableForCatchBlocks) + catchBlock.ExceptionVariable = new ILVariable() { Name = "ex_" + eh.HandlerStart.Offset.ToString("X2"), IsGenerated = true }; + else + catchBlock.ExceptionVariable = null; + catchBlock.Body.RemoveAt(0); + } else { + catchBlock.ExceptionVariable = ldexception.StoreTo[0]; + } + } else { + ILVariable exTemp = new ILVariable() { Name = "ex_" + eh.HandlerStart.Offset.ToString("X2"), IsGenerated = true }; + catchBlock.ExceptionVariable = exTemp; + foreach(ILVariable storeTo in ldexception.StoreTo) { + catchBlock.Body.Insert(0, new ILExpression(ILCode.Stloc, storeTo, new ILExpression(ILCode.Ldloc, exTemp))); + } + } + tryCatchBlock.CatchBlocks.Add(catchBlock); + } else if (eh.HandlerType == ExceptionHandlerType.Finally) { + tryCatchBlock.FinallyBlock = new ILBlock(handlerAst); + } else if (eh.HandlerType == ExceptionHandlerType.Fault) { + tryCatchBlock.FaultBlock = new ILBlock(handlerAst); + } else { + // TODO: ExceptionHandlerType.Filter + } + } + + ehs.ExceptWith(handlers); + + ast.Add(tryCatchBlock); + } + + // Add whatever is left + ast.AddRange(ConvertToAst(body)); + + return ast; + } + + List<ILNode> ConvertToAst(List<ByteCode> body) + { + List<ILNode> ast = new List<ILNode>(); + + // Convert stack-based IL code to ILAst tree + foreach(ByteCode byteCode in body) { + ILRange ilRange = new ILRange(byteCode.Offset, byteCode.EndOffset); + + if (byteCode.StackBefore == null) { + // Unreachable code + continue; + } + + ILExpression expr = new ILExpression(byteCode.Code, byteCode.Operand); + expr.ILRanges.Add(ilRange); + if (byteCode.Prefixes != null && byteCode.Prefixes.Length > 0) { + ILExpressionPrefix[] prefixes = new ILExpressionPrefix[byteCode.Prefixes.Length]; + for (int i = 0; i < prefixes.Length; i++) { + prefixes[i] = new ILExpressionPrefix((ILCode)byteCode.Prefixes[i].OpCode.Code, byteCode.Prefixes[i].Operand); + } + expr.Prefixes = prefixes; + } + + // Label for this instruction + if (byteCode.Label != null) { + ast.Add(byteCode.Label); + } + + // Reference arguments using temporary variables + int popCount = byteCode.PopCount ?? byteCode.StackBefore.Length; + for (int i = byteCode.StackBefore.Length - popCount; i < byteCode.StackBefore.Length; i++) { + StackSlot slot = byteCode.StackBefore[i]; + expr.Arguments.Add(new ILExpression(ILCode.Ldloc, slot.LoadFrom)); + } + + // Store the result to temporary variable(s) if needed + if (byteCode.StoreTo == null || byteCode.StoreTo.Count == 0) { + ast.Add(expr); + } else if (byteCode.StoreTo.Count == 1) { + ast.Add(new ILExpression(ILCode.Stloc, byteCode.StoreTo[0], expr)); + } else { + var offset = byteCode.Offset != 0 ? byteCode.Offset : offsetIdx++; + ILVariable tmpVar = new ILVariable() { Name = "expr_" + offset.ToString("X2"), IsGenerated = true }; + ast.Add(new ILExpression(ILCode.Stloc, tmpVar, expr)); + foreach(ILVariable storeTo in byteCode.StoreTo.AsEnumerable().Reverse()) { + ast.Add(new ILExpression(ILCode.Stloc, storeTo, new ILExpression(ILCode.Ldloc, tmpVar))); + } + } + } + + return ast; + } + } + + public static class ILAstBuilderExtensionMethods + { + public static List<T> CutRange<T>(this List<T> list, int start, int count) + { + List<T> ret = new List<T>(count); + for (int i = 0; i < count; i++) { + ret.Add(list[start + i]); + } + list.RemoveRange(start, count); + return ret; + } + + public static T[] Union<T>(this T[] a, T b) + { + if (a.Length == 0) + return new[] { b }; + if (Array.IndexOf(a, b) >= 0) + return a; + var res = new T[a.Length + 1]; + Array.Copy(a, 0, res, 0, a.Length); + res[res.Length - 1] = b; + return res; + } + + public static T[] Union<T>(this T[] a, T[] b) + { + if (a == b) + return a; + if (a.Length == 0) + return b; + if (b.Length == 0) + return a; + if (a.Length == 1) { + if (b.Length == 1) + return a[0].Equals(b[0]) ? a : new[] { a[0], b[0] }; + return b.Union(a[0]); + } + if (b.Length == 1) + return a.Union(b[0]); + return Enumerable.Union(a, b).ToArray(); + } + } +} diff --git a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs new file mode 100644 index 00000000..876718bc --- /dev/null +++ b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs @@ -0,0 +1,988 @@ +// 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 System.Linq; +using ICSharpCode.Decompiler.FlowAnalysis; +using ICSharpCode.NRefactory.Utils; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.CSharp; + +namespace ICSharpCode.Decompiler.ILAst +{ + public enum ILAstOptimizationStep + { + RemoveRedundantCode, + ReduceBranchInstructionSet, + InlineVariables, + CopyPropagation, + YieldReturn, + AsyncAwait, + PropertyAccessInstructions, + SplitToMovableBlocks, + TypeInference, + HandlePointerArithmetic, + SimplifyShortCircuit, + SimplifyTernaryOperator, + SimplifyNullCoalescing, + JoinBasicBlocks, + SimplifyLogicNot, + SimplifyShiftOperators, + TypeConversionSimplifications, + SimplifyLdObjAndStObj, + SimplifyCustomShortCircuit, + SimplifyLiftedOperators, + TransformArrayInitializers, + TransformMultidimensionalArrayInitializers, + TransformObjectInitializers, + MakeAssignmentExpression, + IntroducePostIncrement, + InlineExpressionTreeParameterDeclarations, + InlineVariables2, + FindLoops, + FindConditions, + FlattenNestedMovableBlocks, + RemoveEndFinally, + RemoveRedundantCode2, + GotoRemoval, + DuplicateReturns, + GotoRemoval2, + ReduceIfNesting, + InlineVariables3, + CachedDelegateInitialization, + IntroduceFixedStatements, + RecombineVariables, + TypeInference2, + RemoveRedundantCode3, + None + } + + public partial class ILAstOptimizer + { + int nextLabelIndex = 0; + + DecompilerContext context; + TypeSystem typeSystem; + ILBlock method; + + public void Optimize(DecompilerContext context, ILBlock method, ILAstOptimizationStep abortBeforeStep = ILAstOptimizationStep.None) + { + this.context = context; + typeSystem = context.CurrentMethod.Module.TypeSystem; + this.method = method; + + if (abortBeforeStep == ILAstOptimizationStep.RemoveRedundantCode) return; + RemoveRedundantCode(method); + + if (abortBeforeStep == ILAstOptimizationStep.ReduceBranchInstructionSet) return; + foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) { + ReduceBranchInstructionSet(block); + } + // ReduceBranchInstructionSet runs before inlining because the non-aggressive inlining heuristic + // looks at which type of instruction consumes the inlined variable. + + if (abortBeforeStep == ILAstOptimizationStep.InlineVariables) return; + // Works better after simple goto removal because of the following debug pattern: stloc X; br Next; Next:; ldloc X + ILInlining inlining1 = new ILInlining(method); + inlining1.InlineAllVariables(); + + if (abortBeforeStep == ILAstOptimizationStep.CopyPropagation) return; + inlining1.CopyPropagation(); + + 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); + + if (abortBeforeStep == ILAstOptimizationStep.SplitToMovableBlocks) return; + foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) { + SplitToBasicBlocks(block); + } + + if (abortBeforeStep == ILAstOptimizationStep.TypeInference) return; + // Types are needed for the ternary operator optimization + TypeAnalysis.Run(context, method); + + if (abortBeforeStep == ILAstOptimizationStep.HandlePointerArithmetic) return; + HandlePointerArithmetic(method); + + foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) { + bool modified; + do { + modified = false; + + if (abortBeforeStep == ILAstOptimizationStep.SimplifyShortCircuit) return; + modified |= block.RunOptimization(new SimpleControlFlow(context, method).SimplifyShortCircuit); + + if (abortBeforeStep == ILAstOptimizationStep.SimplifyTernaryOperator) return; + modified |= block.RunOptimization(new SimpleControlFlow(context, method).SimplifyTernaryOperator); + + if (abortBeforeStep == ILAstOptimizationStep.SimplifyNullCoalescing) return; + modified |= block.RunOptimization(new SimpleControlFlow(context, method).SimplifyNullCoalescing); + + if (abortBeforeStep == ILAstOptimizationStep.JoinBasicBlocks) return; + modified |= block.RunOptimization(new SimpleControlFlow(context, method).JoinBasicBlocks); + + if (abortBeforeStep == ILAstOptimizationStep.SimplifyLogicNot) return; + modified |= block.RunOptimization(SimplifyLogicNot); + + if (abortBeforeStep == ILAstOptimizationStep.SimplifyShiftOperators) return; + modified |= block.RunOptimization(SimplifyShiftOperators); + + if (abortBeforeStep == ILAstOptimizationStep.TypeConversionSimplifications) return; + modified |= block.RunOptimization(TypeConversionSimplifications); + + if (abortBeforeStep == ILAstOptimizationStep.SimplifyLdObjAndStObj) return; + modified |= block.RunOptimization(SimplifyLdObjAndStObj); + + if (abortBeforeStep == ILAstOptimizationStep.SimplifyCustomShortCircuit) return; + modified |= block.RunOptimization(new SimpleControlFlow(context, method).SimplifyCustomShortCircuit); + + if (abortBeforeStep == ILAstOptimizationStep.SimplifyLiftedOperators) return; + modified |= block.RunOptimization(SimplifyLiftedOperators); + + if (abortBeforeStep == ILAstOptimizationStep.TransformArrayInitializers) return; + modified |= block.RunOptimization(TransformArrayInitializers); + + if (abortBeforeStep == ILAstOptimizationStep.TransformMultidimensionalArrayInitializers) return; + modified |= block.RunOptimization(TransformMultidimensionalArrayInitializers); + + if (abortBeforeStep == ILAstOptimizationStep.TransformObjectInitializers) return; + modified |= block.RunOptimization(TransformObjectInitializers); + + if (abortBeforeStep == ILAstOptimizationStep.MakeAssignmentExpression) return; + if (context.Settings.MakeAssignmentExpressions) { + modified |= block.RunOptimization(MakeAssignmentExpression); + } + modified |= block.RunOptimization(MakeCompoundAssignments); + + if (abortBeforeStep == ILAstOptimizationStep.IntroducePostIncrement) return; + if (context.Settings.IntroduceIncrementAndDecrement) { + modified |= block.RunOptimization(IntroducePostIncrement); + } + + if (abortBeforeStep == ILAstOptimizationStep.InlineExpressionTreeParameterDeclarations) return; + if (context.Settings.ExpressionTrees) { + modified |= block.RunOptimization(InlineExpressionTreeParameterDeclarations); + } + + if (abortBeforeStep == ILAstOptimizationStep.InlineVariables2) return; + modified |= new ILInlining(method).InlineAllInBlock(block); + new ILInlining(method).CopyPropagation(); + + } while(modified); + } + + if (abortBeforeStep == ILAstOptimizationStep.FindLoops) return; + foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) { + new LoopsAndConditions(context).FindLoops(block); + } + + if (abortBeforeStep == ILAstOptimizationStep.FindConditions) return; + foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) { + new LoopsAndConditions(context).FindConditions(block); + } + + if (abortBeforeStep == ILAstOptimizationStep.FlattenNestedMovableBlocks) return; + FlattenBasicBlocks(method); + + if (abortBeforeStep == ILAstOptimizationStep.RemoveEndFinally) return; + RemoveEndFinally(method); + + if (abortBeforeStep == ILAstOptimizationStep.RemoveRedundantCode2) return; + RemoveRedundantCode(method); + + if (abortBeforeStep == ILAstOptimizationStep.GotoRemoval) return; + new GotoRemoval().RemoveGotos(method); + + if (abortBeforeStep == ILAstOptimizationStep.DuplicateReturns) return; + DuplicateReturnStatements(method); + + if (abortBeforeStep == ILAstOptimizationStep.GotoRemoval2) return; + new GotoRemoval().RemoveGotos(method); + + if (abortBeforeStep == ILAstOptimizationStep.ReduceIfNesting) return; + ReduceIfNesting(method); + + if (abortBeforeStep == ILAstOptimizationStep.InlineVariables3) return; + // The 2nd inlining pass is necessary because DuplicateReturns and the introduction of ternary operators + // open up additional inlining possibilities. + new ILInlining(method).InlineAllVariables(); + + if (abortBeforeStep == ILAstOptimizationStep.CachedDelegateInitialization) return; + if (context.Settings.AnonymousMethods) { + foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) { + for (int i = 0; i < block.Body.Count; i++) { + // TODO: Move before loops + CachedDelegateInitializationWithField(block, ref i); + CachedDelegateInitializationWithLocal(block, ref i); + } + } + } + + if (abortBeforeStep == ILAstOptimizationStep.IntroduceFixedStatements) return; + // we need post-order traversal, not pre-order, for "fixed" to work correctly + foreach (ILBlock block in TreeTraversal.PostOrder<ILNode>(method, n => n.GetChildren()).OfType<ILBlock>()) { + for (int i = block.Body.Count - 1; i >= 0; i--) { + // TODO: Move before loops + if (i < block.Body.Count) + IntroduceFixedStatements(block.Body, i); + } + } + + if (abortBeforeStep == ILAstOptimizationStep.RecombineVariables) return; + RecombineVariables(method); + + if (abortBeforeStep == ILAstOptimizationStep.TypeInference2) return; + TypeAnalysis.Reset(method); + TypeAnalysis.Run(context, method); + + if (abortBeforeStep == ILAstOptimizationStep.RemoveRedundantCode3) return; + GotoRemoval.RemoveRedundantCode(method); + + // ReportUnassignedILRanges(method); + } + + /// <summary> + /// Removes redundatant Br, Nop, Dup, Pop + /// Ignore arguments of 'leave' + /// </summary> + /// <param name="method"></param> + internal static void RemoveRedundantCode(ILBlock method) + { + Dictionary<ILLabel, int> labelRefCount = new Dictionary<ILLabel, int>(); + foreach (ILLabel target in method.GetSelfAndChildrenRecursive<ILExpression>(e => e.IsBranch()).SelectMany(e => e.GetBranchTargets())) { + labelRefCount[target] = labelRefCount.GetOrDefault(target) + 1; + } + + foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) { + List<ILNode> body = block.Body; + List<ILNode> newBody = new List<ILNode>(body.Count); + for (int i = 0; i < body.Count; i++) { + ILLabel target; + ILExpression popExpr; + if (body[i].Match(ILCode.Br, out target) && i+1 < body.Count && body[i+1] == target) { + // Ignore the branch + if (labelRefCount[target] == 1) + i++; // Ignore the label as well + } else if (body[i].Match(ILCode.Nop)){ + // Ignore nop + } else if (body[i].Match(ILCode.Pop, out popExpr)) { + ILVariable v; + if (!popExpr.Match(ILCode.Ldloc, out v)) + throw new Exception("Pop should have just ldloc at this stage"); + // Best effort to move the ILRange to previous statement + ILVariable prevVar; + ILExpression prevExpr; + if (i - 1 >= 0 && body[i - 1].Match(ILCode.Stloc, out prevVar, out prevExpr) && prevVar == v) + prevExpr.ILRanges.AddRange(((ILExpression)body[i]).ILRanges); + // Ignore pop + } else { + 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; + } + + // Ignore arguments of 'leave' + foreach (ILExpression expr in method.GetSelfAndChildrenRecursive<ILExpression>(e => e.Code == ILCode.Leave)) { + if (expr.Arguments.Any(arg => !arg.Match(ILCode.Ldloc))) + throw new Exception("Leave should have just ldloc at this stage"); + expr.Arguments.Clear(); + } + + // 'dup' removal + foreach (ILExpression expr in method.GetSelfAndChildrenRecursive<ILExpression>()) { + for (int i = 0; i < expr.Arguments.Count; i++) { + ILExpression child; + if (expr.Arguments[i].Match(ILCode.Dup, out child)) { + child.ILRanges.AddRange(expr.Arguments[i].ILRanges); + expr.Arguments[i] = child; + } + } + } + } + + /// <summary> + /// Reduces the branch codes to just br and brtrue. + /// Moves ILRanges to the branch argument + /// </summary> + void ReduceBranchInstructionSet(ILBlock block) + { + for (int i = 0; i < block.Body.Count; i++) { + ILExpression expr = block.Body[i] as ILExpression; + if (expr != null && expr.Prefixes == null) { + ILCode op; + switch(expr.Code) { + case ILCode.Switch: + case ILCode.Brtrue: + expr.Arguments.Single().ILRanges.AddRange(expr.ILRanges); + expr.ILRanges.Clear(); + continue; + case ILCode.__Brfalse: op = ILCode.LogicNot; break; + case ILCode.__Beq: op = ILCode.Ceq; break; + case ILCode.__Bne_Un: op = ILCode.Cne; break; + case ILCode.__Bgt: op = ILCode.Cgt; break; + case ILCode.__Bgt_Un: op = ILCode.Cgt_Un; break; + case ILCode.__Ble: op = ILCode.Cle; break; + case ILCode.__Ble_Un: op = ILCode.Cle_Un; break; + case ILCode.__Blt: op = ILCode.Clt; break; + case ILCode.__Blt_Un: op = ILCode.Clt_Un; break; + case ILCode.__Bge: op = ILCode.Cge; break; + case ILCode.__Bge_Un: op = ILCode.Cge_Un; break; + default: + continue; + } + var newExpr = new ILExpression(op, null, expr.Arguments); + block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, newExpr); + newExpr.ILRanges = expr.ILRanges; + } + } + } + + /// <summary> + /// Converts call and callvirt instructions that read/write properties into CallGetter/CallSetter instructions. + /// + /// CallGetter/CallSetter is used to allow the ILAst to represent "while ((SomeProperty = value) != null)". + /// + /// Also simplifies 'newobj(SomeDelegate, target, ldvirtftn(F, target))' to 'newobj(SomeDelegate, target, ldvirtftn(F))' + /// </summary> + void IntroducePropertyAccessInstructions(ILNode node) + { + ILExpression parentExpr = node as ILExpression; + if (parentExpr != null) { + for (int i = 0; i < parentExpr.Arguments.Count; i++) { + ILExpression expr = parentExpr.Arguments[i]; + IntroducePropertyAccessInstructions(expr); + IntroducePropertyAccessInstructions(expr, parentExpr, i); + } + } else { + foreach (ILNode child in node.GetChildren()) { + IntroducePropertyAccessInstructions(child); + ILExpression expr = child as ILExpression; + if (expr != null) { + IntroducePropertyAccessInstructions(expr, null, -1); + } + } + } + } + + void IntroducePropertyAccessInstructions(ILExpression expr, ILExpression parentExpr, int posInParent) + { + if (expr.Code == ILCode.Call || expr.Code == ILCode.Callvirt) { + MethodReference cecilMethod = (MethodReference)expr.Operand; + if (cecilMethod.DeclaringType is ArrayType) { + switch (cecilMethod.Name) { + case "Get": + expr.Code = ILCode.CallGetter; + break; + case "Set": + expr.Code = ILCode.CallSetter; + break; + case "Address": + ByReferenceType brt = cecilMethod.ReturnType as ByReferenceType; + if (brt != null) { + MethodReference getMethod = new MethodReference("Get", brt.ElementType, cecilMethod.DeclaringType); + foreach (var p in cecilMethod.Parameters) + getMethod.Parameters.Add(p); + getMethod.HasThis = cecilMethod.HasThis; + expr.Operand = getMethod; + } + expr.Code = ILCode.CallGetter; + if (parentExpr != null) { + parentExpr.Arguments[posInParent] = new ILExpression(ILCode.AddressOf, null, expr); + } + break; + } + } else { + MethodDefinition cecilMethodDef = cecilMethod.Resolve(); + if (cecilMethodDef != null) { + if (cecilMethodDef.IsGetter) + expr.Code = (expr.Code == ILCode.Call) ? ILCode.CallGetter : ILCode.CallvirtGetter; + else if (cecilMethodDef.IsSetter) + expr.Code = (expr.Code == ILCode.Call) ? ILCode.CallSetter : ILCode.CallvirtSetter; + } + } + } else if (expr.Code == ILCode.Newobj && expr.Arguments.Count == 2) { + // Might be 'newobj(SomeDelegate, target, ldvirtftn(F, target))'. + ILVariable target; + if (expr.Arguments[0].Match(ILCode.Ldloc, out target) + && expr.Arguments[1].Code == ILCode.Ldvirtftn + && expr.Arguments[1].Arguments.Count == 1 + && expr.Arguments[1].Arguments[0].MatchLdloc(target)) + { + // Remove the 'target' argument from the ldvirtftn instruction. + // It's not needed in the translation to C#, and needs to be eliminated so that the target expression + // can be inlined. + expr.Arguments[1].Arguments.Clear(); + } + } + } + + /// <summary> + /// Group input into a set of blocks that can be later arbitraliby schufled. + /// The method adds necessary branches to make control flow between blocks + /// explicit and thus order independent. + /// </summary> + void SplitToBasicBlocks(ILBlock block) + { + List<ILNode> basicBlocks = new List<ILNode>(); + + ILLabel entryLabel = block.Body.FirstOrDefault() as ILLabel ?? new ILLabel() { Name = "Block_" + (nextLabelIndex++) }; + ILBasicBlock basicBlock = new ILBasicBlock(); + basicBlocks.Add(basicBlock); + basicBlock.Body.Add(entryLabel); + block.EntryGoto = new ILExpression(ILCode.Br, entryLabel); + + if (block.Body.Count > 0) { + if (block.Body[0] != entryLabel) + basicBlock.Body.Add(block.Body[0]); + + for (int i = 1; i < block.Body.Count; i++) { + ILNode lastNode = block.Body[i - 1]; + ILNode currNode = block.Body[i]; + + // Start a new basic block if necessary + if (currNode is ILLabel || + currNode is ILTryCatchBlock || // Counts as label + lastNode.IsConditionalControlFlow() || + lastNode.IsUnconditionalControlFlow()) + { + // Try to reuse the label + ILLabel label = currNode as ILLabel ?? new ILLabel() { Name = "Block_" + (nextLabelIndex++).ToString() }; + + // Terminate the last block + if (!lastNode.IsUnconditionalControlFlow()) { + // Explicit branch from one block to other + basicBlock.Body.Add(new ILExpression(ILCode.Br, label)); + } + + // Start the new block + basicBlock = new ILBasicBlock(); + basicBlocks.Add(basicBlock); + basicBlock.Body.Add(label); + + // Add the node to the basic block + if (currNode != label) + basicBlock.Body.Add(currNode); + } else { + basicBlock.Body.Add(currNode); + } + } + } + + block.Body = basicBlocks; + return; + } + + void DuplicateReturnStatements(ILBlock method) + { + Dictionary<ILLabel, ILNode> nextSibling = new Dictionary<ILLabel, ILNode>(); + + // Build navigation data + foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) { + for (int i = 0; i < block.Body.Count - 1; i++) { + ILLabel curr = block.Body[i] as ILLabel; + if (curr != null) { + nextSibling[curr] = block.Body[i + 1]; + } + } + } + + // Duplicate returns + foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) { + for (int i = 0; i < block.Body.Count; i++) { + ILLabel targetLabel; + if (block.Body[i].Match(ILCode.Br, out targetLabel) || block.Body[i].Match(ILCode.Leave, out targetLabel)) { + // Skip extra labels + while(nextSibling.ContainsKey(targetLabel) && nextSibling[targetLabel] is ILLabel) { + targetLabel = (ILLabel)nextSibling[targetLabel]; + } + + // Inline return statement + ILNode target; + List<ILExpression> retArgs; + if (nextSibling.TryGetValue(targetLabel, out target)) { + if (target.Match(ILCode.Ret, out retArgs)) { + ILVariable locVar; + object constValue; + if (retArgs.Count == 0) { + block.Body[i] = new ILExpression(ILCode.Ret, null); + } else if (retArgs.Single().Match(ILCode.Ldloc, out locVar)) { + block.Body[i] = new ILExpression(ILCode.Ret, null, new ILExpression(ILCode.Ldloc, locVar)); + } else if (retArgs.Single().Match(ILCode.Ldc_I4, out constValue)) { + block.Body[i] = new ILExpression(ILCode.Ret, null, new ILExpression(ILCode.Ldc_I4, constValue)); + } + } + } else { + if (method.Body.Count > 0 && method.Body.Last() == targetLabel) { + // It exits the main method - so it is same as return; + block.Body[i] = new ILExpression(ILCode.Ret, null); + } + } + } + } + } + } + + /// <summary> + /// Flattens all nested basic blocks, except the the top level 'node' argument + /// </summary> + void FlattenBasicBlocks(ILNode node) + { + ILBlock block = node as ILBlock; + if (block != null) { + List<ILNode> flatBody = new List<ILNode>(); + foreach (ILNode child in block.GetChildren()) { + FlattenBasicBlocks(child); + ILBasicBlock childAsBB = child as ILBasicBlock; + if (childAsBB != null) { + if (!(childAsBB.Body.FirstOrDefault() is ILLabel)) + throw new Exception("Basic block has to start with a label. \n" + childAsBB.ToString()); + if (childAsBB.Body.LastOrDefault() is ILExpression && !childAsBB.Body.LastOrDefault().IsUnconditionalControlFlow()) + throw new Exception("Basci block has to end with unconditional control flow. \n" + childAsBB.ToString()); + flatBody.AddRange(childAsBB.GetChildren()); + } else { + flatBody.Add(child); + } + } + block.EntryGoto = null; + block.Body = flatBody; + } else if (node is ILExpression) { + // Optimization - no need to check expressions + } else if (node != null) { + // Recursively find all ILBlocks + foreach(ILNode child in node.GetChildren()) { + FlattenBasicBlocks(child); + } + } + } + + /// <summary> + /// Replace endfinally with jump to the end of the finally block + /// </summary> + void RemoveEndFinally(ILBlock method) + { + // Go thought the list in reverse so that we do the nested blocks first + foreach(var tryCatch in method.GetSelfAndChildrenRecursive<ILTryCatchBlock>(tc => tc.FinallyBlock != null).Reverse()) { + ILLabel label = new ILLabel() { Name = "EndFinally_" + nextLabelIndex++ }; + tryCatch.FinallyBlock.Body.Add(label); + foreach(var block in tryCatch.FinallyBlock.GetSelfAndChildrenRecursive<ILBlock>()) { + for (int i = 0; i < block.Body.Count; i++) { + if (block.Body[i].Match(ILCode.Endfinally)) { + block.Body[i] = new ILExpression(ILCode.Br, label).WithILRanges(((ILExpression)block.Body[i]).ILRanges); + } + } + } + } + } + + /// <summary> + /// Reduce the nesting of conditions. + /// It should be done on flat data that already had most gotos removed + /// </summary> + void ReduceIfNesting(ILNode node) + { + ILBlock block = node as ILBlock; + if (block != null) { + for (int i = 0; i < block.Body.Count; i++) { + ILCondition cond = block.Body[i] as ILCondition; + if (cond != null) { + bool trueExits = cond.TrueBlock.Body.LastOrDefault().IsUnconditionalControlFlow(); + bool falseExits = cond.FalseBlock.Body.LastOrDefault().IsUnconditionalControlFlow(); + + if (trueExits) { + // Move the false block after the condition + block.Body.InsertRange(i + 1, cond.FalseBlock.GetChildren()); + cond.FalseBlock = new ILBlock(); + } else if (falseExits) { + // Move the true block after the condition + block.Body.InsertRange(i + 1, cond.TrueBlock.GetChildren()); + cond.TrueBlock = new ILBlock(); + } + + // Eliminate empty true block + if (!cond.TrueBlock.GetChildren().Any() && cond.FalseBlock.GetChildren().Any()) { + // Swap bodies + ILBlock tmp = cond.TrueBlock; + cond.TrueBlock = cond.FalseBlock; + cond.FalseBlock = tmp; + cond.Condition = new ILExpression(ILCode.LogicNot, null, cond.Condition); + } + } + } + } + + // We are changing the number of blocks so we use plain old recursion to get all blocks + foreach(ILNode child in node.GetChildren()) { + if (child != null && !(child is ILExpression)) + ReduceIfNesting(child); + } + } + + void RecombineVariables(ILBlock method) + { + // Recombine variables that were split when the ILAst was created + // This ensures that a single IL variable is a single C# variable (gets assigned only one name) + // The DeclareVariables transformation might then split up the C# variable again if it is used indendently in two separate scopes. + Dictionary<VariableDefinition, ILVariable> dict = new Dictionary<VariableDefinition, ILVariable>(); + ReplaceVariables( + method, + delegate(ILVariable v) { + if (v.OriginalVariable == null) + return v; + ILVariable combinedVariable; + if (!dict.TryGetValue(v.OriginalVariable, out combinedVariable)) { + dict.Add(v.OriginalVariable, v); + combinedVariable = v; + } + return combinedVariable; + }); + } + + static void HandlePointerArithmetic(ILNode method) + { + foreach (ILExpression expr in method.GetSelfAndChildrenRecursive<ILExpression>()) { + List<ILExpression> args = expr.Arguments; + switch (expr.Code) { + case ILCode.Localloc: + args[0] = DivideBySize(args[0], ((PointerType)expr.InferredType).ElementType); + break; + case ILCode.Add: + case ILCode.Add_Ovf: + case ILCode.Add_Ovf_Un: + if (expr.InferredType is PointerType) { + if (args[0].ExpectedType is PointerType) + args[1] = DivideBySize(args[1], ((PointerType)expr.InferredType).ElementType); + else if (args[1].ExpectedType is PointerType) + args[0] = DivideBySize(args[0], ((PointerType)expr.InferredType).ElementType); + } + break; + case ILCode.Sub: + case ILCode.Sub_Ovf: + case ILCode.Sub_Ovf_Un: + if (expr.InferredType is PointerType) { + if (args[0].ExpectedType is PointerType) + args[1] = DivideBySize(args[1], ((PointerType)expr.InferredType).ElementType); + } + break; + } + } + } + + static ILExpression UnwrapIntPtrCast(ILExpression expr) + { + if (expr.Code != ILCode.Conv_I && expr.Code != ILCode.Conv_U) + return expr; + + ILExpression arg = expr.Arguments[0]; + switch (arg.InferredType.MetadataType) { + case MetadataType.Byte: + case MetadataType.SByte: + case MetadataType.UInt16: + case MetadataType.Int16: + case MetadataType.UInt32: + case MetadataType.Int32: + case MetadataType.UInt64: + case MetadataType.Int64: + return arg; + } + + return expr; + } + + static ILExpression DivideBySize(ILExpression expr, TypeReference type) + { + expr = UnwrapIntPtrCast(expr); + + ILExpression sizeOfExpression; + switch (TypeAnalysis.GetInformationAmount(type)) { + case 1: + case 8: + sizeOfExpression = new ILExpression(ILCode.Ldc_I4, 1); + break; + case 16: + sizeOfExpression = new ILExpression(ILCode.Ldc_I4, 2); + break; + case 32: + sizeOfExpression = new ILExpression(ILCode.Ldc_I4, 4); + break; + case 64: + sizeOfExpression = new ILExpression(ILCode.Ldc_I4, 8); + break; + default: + sizeOfExpression = new ILExpression(ILCode.Sizeof, type); + break; + } + + if (expr.Code == ILCode.Mul || expr.Code == ILCode.Mul_Ovf || expr.Code == ILCode.Mul_Ovf_Un) { + ILExpression mulArg = expr.Arguments[1]; + if (mulArg.Code == sizeOfExpression.Code && sizeOfExpression.Operand.Equals(mulArg.Operand)) + return UnwrapIntPtrCast(expr.Arguments[0]); + } + + if (expr.Code == sizeOfExpression.Code) { + if (sizeOfExpression.Operand.Equals(expr.Operand)) + return new ILExpression(ILCode.Ldc_I4, 1); + + if (expr.Code == ILCode.Ldc_I4) { + int offsetInBytes = (int)expr.Operand; + int elementSize = (int)sizeOfExpression.Operand; + int offsetInElements = offsetInBytes / elementSize; + + // ensure integer division + if (offsetInElements * elementSize == offsetInBytes) { + expr.Operand = offsetInElements; + return expr; + } + } + } + + return new ILExpression(ILCode.Div_Un, null, expr, sizeOfExpression); + } + + public static void ReplaceVariables(ILNode node, Func<ILVariable, ILVariable> variableMapping) + { + ILExpression expr = node as ILExpression; + if (expr != null) { + ILVariable v = expr.Operand as ILVariable; + if (v != null) + expr.Operand = variableMapping(v); + foreach (ILExpression child in expr.Arguments) + ReplaceVariables(child, variableMapping); + } else { + var catchBlock = node as ILTryCatchBlock.CatchBlock; + if (catchBlock != null && catchBlock.ExceptionVariable != null) { + catchBlock.ExceptionVariable = variableMapping(catchBlock.ExceptionVariable); + } + + foreach (ILNode child in node.GetChildren()) + ReplaceVariables(child, variableMapping); + } + } + + void ReportUnassignedILRanges(ILBlock method) + { + var unassigned = ILRange.Invert(method.GetSelfAndChildrenRecursive<ILExpression>().SelectMany(e => e.ILRanges), context.CurrentMethod.Body.CodeSize).ToList(); + if (unassigned.Count > 0) + Debug.WriteLine(string.Format("Unassigned ILRanges for {0}.{1}: {2}", context.CurrentMethod.DeclaringType.Name, context.CurrentMethod.Name, string.Join(", ", unassigned.Select(r => r.ToString())))); + } + } + + public static class ILAstOptimizerExtensionMethods + { + /// <summary> + /// Perform one pass of a given optimization on this block. + /// This block must consist of only basicblocks. + /// </summary> + public static bool RunOptimization(this ILBlock block, Func<List<ILNode>, ILBasicBlock, int, bool> optimization) + { + bool modified = false; + List<ILNode> body = block.Body; + for (int i = body.Count - 1; i >= 0; i--) { + if (i < body.Count && optimization(body, (ILBasicBlock)body[i], i)) { + modified = true; + } + } + return modified; + } + + public static bool RunOptimization(this ILBlock block, Func<List<ILNode>, ILExpression, int, bool> optimization) + { + bool modified = false; + foreach (ILBasicBlock bb in block.Body) { + for (int i = bb.Body.Count - 1; i >= 0; i--) { + ILExpression expr = bb.Body.ElementAtOrDefault(i) as ILExpression; + if (expr != null && optimization(bb.Body, expr, i)) { + modified = true; + } + } + } + return modified; + } + + public static bool IsConditionalControlFlow(this ILNode node) + { + ILExpression expr = node as ILExpression; + return expr != null && expr.Code.IsConditionalControlFlow(); + } + + public static bool IsUnconditionalControlFlow(this ILNode node) + { + ILExpression expr = node as ILExpression; + return expr != null && expr.Code.IsUnconditionalControlFlow(); + } + + /// <summary> + /// The expression has no effect on the program and can be removed + /// if its return value is not needed. + /// </summary> + public static bool HasNoSideEffects(this ILExpression expr) + { + // Remember that if expression can throw an exception, it is a side effect + + switch(expr.Code) { + case ILCode.Ldloc: + case ILCode.Ldloca: + case ILCode.Ldstr: + case ILCode.Ldnull: + case ILCode.Ldc_I4: + case ILCode.Ldc_I8: + case ILCode.Ldc_R4: + case ILCode.Ldc_R8: + case ILCode.Ldc_Decimal: + return true; + default: + return false; + } + } + + public static bool IsStoreToArray(this ILCode code) + { + switch (code) { + case ILCode.Stelem_Any: + case ILCode.Stelem_I: + case ILCode.Stelem_I1: + case ILCode.Stelem_I2: + case ILCode.Stelem_I4: + case ILCode.Stelem_I8: + case ILCode.Stelem_R4: + case ILCode.Stelem_R8: + case ILCode.Stelem_Ref: + return true; + default: + return false; + } + } + + public static bool IsLoadFromArray(this ILCode code) + { + switch (code) { + case ILCode.Ldelem_Any: + case ILCode.Ldelem_I: + case ILCode.Ldelem_I1: + case ILCode.Ldelem_I2: + case ILCode.Ldelem_I4: + case ILCode.Ldelem_I8: + case ILCode.Ldelem_U1: + case ILCode.Ldelem_U2: + case ILCode.Ldelem_U4: + case ILCode.Ldelem_R4: + case ILCode.Ldelem_R8: + case ILCode.Ldelem_Ref: + return true; + default: + return false; + } + } + + /// <summary> + /// Can the expression be used as a statement in C#? + /// </summary> + public static bool CanBeExpressionStatement(this ILExpression expr) + { + switch(expr.Code) { + case ILCode.Call: + case ILCode.Callvirt: + // property getters can't be expression statements, but all other method calls can be + MethodReference mr = (MethodReference)expr.Operand; + return !mr.Name.StartsWith("get_", StringComparison.Ordinal); + case ILCode.CallSetter: + case ILCode.CallvirtSetter: + case ILCode.Newobj: + case ILCode.Newarr: + case ILCode.Stloc: + case ILCode.Stobj: + case ILCode.Stsfld: + case ILCode.Stfld: + case ILCode.Stind_Ref: + case ILCode.Stelem_Any: + case ILCode.Stelem_I: + case ILCode.Stelem_I1: + case ILCode.Stelem_I2: + case ILCode.Stelem_I4: + case ILCode.Stelem_I8: + case ILCode.Stelem_R4: + case ILCode.Stelem_R8: + case ILCode.Stelem_Ref: + return true; + default: + return false; + } + } + + public static ILExpression WithILRanges(this ILExpression expr, IEnumerable<ILRange> ilranges) + { + expr.ILRanges.AddRange(ilranges); + return expr; + } + + public static void RemoveTail(this List<ILNode> body, params ILCode[] codes) + { + for (int i = 0; i < codes.Length; i++) { + if (((ILExpression)body[body.Count - codes.Length + i]).Code != codes[i]) + throw new Exception("Tailing code does not match expected."); + } + body.RemoveRange(body.Count - codes.Length, codes.Length); + } + + public static V GetOrDefault<K,V>(this Dictionary<K, V> dict, K key) + { + V ret; + dict.TryGetValue(key, out ret); + return ret; + } + + public static void RemoveOrThrow<T>(this ICollection<T> collection, T item) + { + if (!collection.Remove(item)) + throw new Exception("The item was not found in the collection"); + } + + public static void RemoveOrThrow<K,V>(this Dictionary<K,V> collection, K key) + { + if (!collection.Remove(key)) + throw new Exception("The key was not found in the dictionary"); + } + + public static bool ContainsReferenceTo(this ILExpression expr, ILVariable v) + { + if (expr.Operand == v) + return true; + foreach (var arg in expr.Arguments) { + if (ContainsReferenceTo(arg, v)) + return true; + } + return false; + } + } +} diff --git a/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs b/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs new file mode 100644 index 00000000..aab64e6e --- /dev/null +++ b/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs @@ -0,0 +1,601 @@ +// 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 System.IO; +using System.Linq; +using System.Text; +using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.Disassembler; +using ICSharpCode.NRefactory.Utils; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.CSharp; +using Cecil = Mono.Cecil; + +namespace ICSharpCode.Decompiler.ILAst +{ + public abstract class ILNode + { + public IEnumerable<T> GetSelfAndChildrenRecursive<T>(Func<T, bool> predicate = null) where T: ILNode + { + List<T> result = new List<T>(16); + AccumulateSelfAndChildrenRecursive(result, predicate); + return result; + } + + void AccumulateSelfAndChildrenRecursive<T>(List<T> list, Func<T, bool> predicate) where T:ILNode + { + // Note: RemoveEndFinally depends on self coming before children + T thisAsT = this as T; + if (thisAsT != null && (predicate == null || predicate(thisAsT))) + list.Add(thisAsT); + foreach (ILNode node in GetChildren()) { + if (node != null) + node.AccumulateSelfAndChildrenRecursive(list, predicate); + } + } + + public virtual IEnumerable<ILNode> GetChildren() + { + yield break; + } + + public override string ToString() + { + StringWriter w = new StringWriter(); + WriteTo(new PlainTextOutput(w)); + return w.ToString().Replace("\r\n", "; "); + } + + public abstract void WriteTo(ITextOutput output); + } + + public class ILBlock: ILNode + { + public ILExpression EntryGoto; + + public List<ILNode> Body; + + public ILBlock(params ILNode[] body) + { + Body = new List<ILNode>(body); + } + + public ILBlock(List<ILNode> body) + { + Body = body; + } + + public override IEnumerable<ILNode> GetChildren() + { + if (EntryGoto != null) + yield return EntryGoto; + foreach(ILNode child in Body) { + yield return child; + } + } + + public override void WriteTo(ITextOutput output) + { + foreach(ILNode child in GetChildren()) { + child.WriteTo(output); + output.WriteLine(); + } + } + } + + public class ILBasicBlock: ILNode + { + /// <remarks> Body has to start with a label and end with unconditional control flow </remarks> + public List<ILNode> Body = new List<ILNode>(); + + public override IEnumerable<ILNode> GetChildren() + { + return Body; + } + + public override void WriteTo(ITextOutput output) + { + foreach(ILNode child in GetChildren()) { + child.WriteTo(output); + output.WriteLine(); + } + } + } + + public class ILLabel: ILNode + { + public string Name; + + public override void WriteTo(ITextOutput output) + { + output.WriteDefinition(Name + ":", this); + } + } + + public class ILTryCatchBlock: ILNode + { + public class CatchBlock: ILBlock + { + public TypeReference ExceptionType; + public ILVariable ExceptionVariable; + + public override void WriteTo(ITextOutput output) + { + output.Write("catch "); + output.WriteReference(ExceptionType.FullName, ExceptionType); + if (ExceptionVariable != null) { + output.Write(' '); + output.Write(ExceptionVariable.Name); + } + output.WriteLine(" {"); + output.Indent(); + base.WriteTo(output); + output.Unindent(); + output.WriteLine("}"); + } + } + + public ILBlock TryBlock; + public List<CatchBlock> CatchBlocks; + public ILBlock FinallyBlock; + public ILBlock FaultBlock; + + public override IEnumerable<ILNode> GetChildren() + { + if (TryBlock != null) + yield return TryBlock; + foreach (var catchBlock in CatchBlocks) { + yield return catchBlock; + } + if (FaultBlock != null) + yield return FaultBlock; + if (FinallyBlock != null) + yield return FinallyBlock; + } + + public override void WriteTo(ITextOutput output) + { + output.WriteLine(".try {"); + output.Indent(); + TryBlock.WriteTo(output); + output.Unindent(); + output.WriteLine("}"); + foreach (CatchBlock block in CatchBlocks) { + block.WriteTo(output); + } + if (FaultBlock != null) { + output.WriteLine("fault {"); + output.Indent(); + FaultBlock.WriteTo(output); + output.Unindent(); + output.WriteLine("}"); + } + if (FinallyBlock != null) { + output.WriteLine("finally {"); + output.Indent(); + FinallyBlock.WriteTo(output); + output.Unindent(); + output.WriteLine("}"); + } + } + } + + public class ILVariable + { + public string Name; + public bool IsGenerated; + public TypeReference Type; + public VariableDefinition OriginalVariable; + public ParameterDefinition OriginalParameter; + + public bool IsPinned { + get { return OriginalVariable != null && OriginalVariable.IsPinned; } + } + + public bool IsParameter { + get { return OriginalParameter != null; } + } + + public override string ToString() + { + return Name; + } + } + + public struct ILRange + { + public readonly int From; + public readonly int To; // Exlusive + + public ILRange(int @from, int to) + { + From = @from; + To = to; + } + + public override string ToString() + { + return string.Format("{0:X2}-{1:X2}", From, To); + } + + public static List<ILRange> OrderAndJoin(IEnumerable<ILRange> input) + { + if (input == null) + throw new ArgumentNullException("Input is null!"); + + List<ILRange> result = new List<ILRange>(); + foreach(ILRange curr in input.OrderBy(r => r.From)) { + if (result.Count > 0) { + // Merge consequtive ranges if possible + ILRange last = result[result.Count - 1]; + if (curr.From <= last.To) { + result[result.Count - 1] = new ILRange(last.From, Math.Max(last.To, curr.To)); + continue; + } + } + result.Add(curr); + } + return result; + } + + public static List<ILRange> Invert(IEnumerable<ILRange> input, int codeSize) + { + if (input == null) + throw new ArgumentNullException("Input is null!"); + + if (codeSize <= 0) + throw new ArgumentException("Code size must be grater than 0"); + + List<ILRange> ordered = OrderAndJoin(input); + List<ILRange> result = new List<ILRange>(ordered.Count + 1); + if (ordered.Count == 0) { + result.Add(new ILRange(0, codeSize)); + } else { + // Gap before the first element + if (ordered.First().From != 0) + result.Add(new ILRange(0, ordered.First().From)); + + // Gaps between elements + for (int i = 0; i < ordered.Count - 1; i++) + result.Add(new ILRange(ordered[i].To, ordered[i + 1].From)); + + // Gap after the last element + Debug.Assert(ordered.Last().To <= codeSize); + if (ordered.Last().To != codeSize) + result.Add(new ILRange(ordered.Last().To, codeSize)); + } + return result; + } + } + + public class ILExpressionPrefix + { + public readonly ILCode Code; + public readonly object Operand; + + public ILExpressionPrefix(ILCode code, object operand = null) + { + Code = code; + Operand = operand; + } + } + + public class ILExpression : ILNode + { + public ILCode Code { get; set; } + public object Operand { get; set; } + public List<ILExpression> Arguments { get; set; } + public ILExpressionPrefix[] Prefixes { get; set; } + // Mapping to the original instructions (useful for debugging) + public List<ILRange> ILRanges { get; set; } + + public TypeReference ExpectedType { get; set; } + public TypeReference InferredType { get; set; } + + public static readonly object AnyOperand = new object(); + + public ILExpression(ILCode code, object operand, List<ILExpression> args) + { + if (operand is ILExpression) + throw new ArgumentException("operand"); + + Code = code; + Operand = operand; + Arguments = new List<ILExpression>(args); + ILRanges = new List<ILRange>(1); + } + + public ILExpression(ILCode code, object operand, params ILExpression[] args) + { + if (operand is ILExpression) + throw new ArgumentException("operand"); + + Code = code; + Operand = operand; + Arguments = new List<ILExpression>(args); + ILRanges = new List<ILRange>(1); + } + + public void AddPrefix(ILExpressionPrefix prefix) + { + ILExpressionPrefix[] arr = Prefixes; + if (arr == null) + arr = new ILExpressionPrefix[1]; + else + Array.Resize(ref arr, arr.Length + 1); + arr[arr.Length - 1] = prefix; + Prefixes = arr; + } + + public ILExpressionPrefix GetPrefix(ILCode code) + { + var prefixes = Prefixes; + if (prefixes != null) { + foreach (ILExpressionPrefix p in prefixes) { + if (p.Code == code) + return p; + } + } + return null; + } + + public override IEnumerable<ILNode> GetChildren() + { + return Arguments; + } + + public bool IsBranch() + { + return Operand is ILLabel || Operand is ILLabel[]; + } + + public IEnumerable<ILLabel> GetBranchTargets() + { + if (Operand is ILLabel) { + return new ILLabel[] { (ILLabel)Operand }; + } else if (Operand is ILLabel[]) { + return (ILLabel[])Operand; + } else { + return new ILLabel[] { }; + } + } + + public override void WriteTo(ITextOutput output) + { + if (Operand is ILVariable && ((ILVariable)Operand).IsGenerated) { + if (Code == ILCode.Stloc && InferredType == null) { + output.Write(((ILVariable)Operand).Name); + output.Write(" = "); + Arguments.First().WriteTo(output); + return; + } else if (Code == ILCode.Ldloc) { + output.Write(((ILVariable)Operand).Name); + if (InferredType != null) { + output.Write(':'); + InferredType.WriteTo(output, ILNameSyntax.ShortTypeName); + if (ExpectedType != null && ExpectedType.FullName != InferredType.FullName) { + output.Write("[exp:"); + ExpectedType.WriteTo(output, ILNameSyntax.ShortTypeName); + output.Write(']'); + } + } + return; + } + } + + if (Prefixes != null) { + foreach (var prefix in Prefixes) { + output.Write(prefix.Code.GetName()); + output.Write(". "); + } + } + + output.Write(Code.GetName()); + if (InferredType != null) { + output.Write(':'); + InferredType.WriteTo(output, ILNameSyntax.ShortTypeName); + if (ExpectedType != null && ExpectedType.FullName != InferredType.FullName) { + output.Write("[exp:"); + ExpectedType.WriteTo(output, ILNameSyntax.ShortTypeName); + output.Write(']'); + } + } else if (ExpectedType != null) { + output.Write("[exp:"); + ExpectedType.WriteTo(output, ILNameSyntax.ShortTypeName); + output.Write(']'); + } + output.Write('('); + bool first = true; + if (Operand != null) { + if (Operand is ILLabel) { + output.WriteReference(((ILLabel)Operand).Name, Operand); + } else if (Operand is ILLabel[]) { + ILLabel[] labels = (ILLabel[])Operand; + for (int i = 0; i < labels.Length; i++) { + if (i > 0) + output.Write(", "); + output.WriteReference(labels[i].Name, labels[i]); + } + } else if (Operand is MethodReference) { + MethodReference method = (MethodReference)Operand; + if (method.DeclaringType != null) { + method.DeclaringType.WriteTo(output, ILNameSyntax.ShortTypeName); + output.Write("::"); + } + output.WriteReference(method.Name, method); + } else if (Operand is FieldReference) { + FieldReference field = (FieldReference)Operand; + field.DeclaringType.WriteTo(output, ILNameSyntax.ShortTypeName); + output.Write("::"); + output.WriteReference(field.Name, field); + } else { + DisassemblerHelpers.WriteOperand(output, Operand); + } + first = false; + } + foreach (ILExpression arg in Arguments) { + if (!first) output.Write(", "); + arg.WriteTo(output); + first = false; + } + output.Write(')'); + } + } + + public class ILWhileLoop : ILNode + { + public ILExpression Condition; + public ILBlock BodyBlock; + + public override IEnumerable<ILNode> GetChildren() + { + if (Condition != null) + yield return Condition; + if (BodyBlock != null) + yield return BodyBlock; + } + + public override void WriteTo(ITextOutput output) + { + output.WriteLine(""); + output.Write("loop ("); + if (Condition != null) + Condition.WriteTo(output); + output.WriteLine(") {"); + output.Indent(); + BodyBlock.WriteTo(output); + output.Unindent(); + output.WriteLine("}"); + } + } + + public class ILCondition : ILNode + { + public ILExpression Condition; + public ILBlock TrueBlock; // Branch was taken + public ILBlock FalseBlock; // Fall-though + + public override IEnumerable<ILNode> GetChildren() + { + if (Condition != null) + yield return Condition; + if (TrueBlock != null) + yield return TrueBlock; + if (FalseBlock != null) + yield return FalseBlock; + } + + public override void WriteTo(ITextOutput output) + { + output.Write("if ("); + Condition.WriteTo(output); + output.WriteLine(") {"); + output.Indent(); + TrueBlock.WriteTo(output); + output.Unindent(); + output.Write("}"); + if (FalseBlock != null) { + output.WriteLine(" else {"); + output.Indent(); + FalseBlock.WriteTo(output); + output.Unindent(); + output.WriteLine("}"); + } + } + } + + public class ILSwitch: ILNode + { + public class CaseBlock: ILBlock + { + public List<int> Values; // null for the default case + + public override void WriteTo(ITextOutput output) + { + if (Values != null) { + foreach (int i in Values) { + output.WriteLine("case {0}:", i); + } + } else { + output.WriteLine("default:"); + } + output.Indent(); + base.WriteTo(output); + output.Unindent(); + } + } + + public ILExpression Condition; + public List<CaseBlock> CaseBlocks = new List<CaseBlock>(); + + public override IEnumerable<ILNode> GetChildren() + { + if (Condition != null) + yield return Condition; + foreach (ILBlock caseBlock in CaseBlocks) { + yield return caseBlock; + } + } + + public override void WriteTo(ITextOutput output) + { + output.Write("switch ("); + Condition.WriteTo(output); + output.WriteLine(") {"); + output.Indent(); + foreach (CaseBlock caseBlock in CaseBlocks) { + caseBlock.WriteTo(output); + } + output.Unindent(); + output.WriteLine("}"); + } + } + + public class ILFixedStatement : ILNode + { + public List<ILExpression> Initializers = new List<ILExpression>(); + public ILBlock BodyBlock; + + public override IEnumerable<ILNode> GetChildren() + { + foreach (ILExpression initializer in Initializers) + yield return initializer; + if (BodyBlock != null) + yield return BodyBlock; + } + + public override void WriteTo(ITextOutput output) + { + output.Write("fixed ("); + for (int i = 0; i < Initializers.Count; i++) { + if (i > 0) + output.Write(", "); + Initializers[i].WriteTo(output); + } + output.WriteLine(") {"); + output.Indent(); + BodyBlock.WriteTo(output); + output.Unindent(); + output.WriteLine("}"); + } + } +}
\ No newline at end of file diff --git a/ICSharpCode.Decompiler/ILAst/ILCodes.cs b/ICSharpCode.Decompiler/ILAst/ILCodes.cs new file mode 100644 index 00000000..5ca063b7 --- /dev/null +++ b/ICSharpCode.Decompiler/ILAst/ILCodes.cs @@ -0,0 +1,490 @@ +// 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 Mono.Cecil; +using Mono.Cecil.Cil; + +namespace ICSharpCode.Decompiler.ILAst +{ + public enum ILCode + { + // For convenience, the start is exactly identical to Mono.Cecil.Cil.Code + // Instructions that should not be used are prepended by __ + Nop, + Break, + __Ldarg_0, + __Ldarg_1, + __Ldarg_2, + __Ldarg_3, + __Ldloc_0, + __Ldloc_1, + __Ldloc_2, + __Ldloc_3, + __Stloc_0, + __Stloc_1, + __Stloc_2, + __Stloc_3, + __Ldarg_S, + __Ldarga_S, + __Starg_S, + __Ldloc_S, + __Ldloca_S, + __Stloc_S, + Ldnull, + __Ldc_I4_M1, + __Ldc_I4_0, + __Ldc_I4_1, + __Ldc_I4_2, + __Ldc_I4_3, + __Ldc_I4_4, + __Ldc_I4_5, + __Ldc_I4_6, + __Ldc_I4_7, + __Ldc_I4_8, + __Ldc_I4_S, + Ldc_I4, + Ldc_I8, + Ldc_R4, + Ldc_R8, + Dup, + Pop, + Jmp, + Call, + Calli, + Ret, + __Br_S, + __Brfalse_S, + __Brtrue_S, + __Beq_S, + __Bge_S, + __Bgt_S, + __Ble_S, + __Blt_S, + __Bne_Un_S, + __Bge_Un_S, + __Bgt_Un_S, + __Ble_Un_S, + __Blt_Un_S, + Br, + __Brfalse, + Brtrue, + __Beq, + __Bge, + __Bgt, + __Ble, + __Blt, + __Bne_Un, + __Bge_Un, + __Bgt_Un, + __Ble_Un, + __Blt_Un, + Switch, + __Ldind_I1, + __Ldind_U1, + __Ldind_I2, + __Ldind_U2, + __Ldind_I4, + __Ldind_U4, + __Ldind_I8, + __Ldind_I, + __Ldind_R4, + __Ldind_R8, + Ldind_Ref, + Stind_Ref, + __Stind_I1, + __Stind_I2, + __Stind_I4, + __Stind_I8, + __Stind_R4, + __Stind_R8, + Add, + Sub, + Mul, + Div, + Div_Un, + Rem, + Rem_Un, + And, + Or, + Xor, + Shl, + Shr, + Shr_Un, + Neg, + Not, + Conv_I1, + Conv_I2, + Conv_I4, + Conv_I8, + Conv_R4, + Conv_R8, + Conv_U4, + Conv_U8, + Callvirt, + Cpobj, + Ldobj, + Ldstr, + Newobj, + Castclass, + Isinst, + Conv_R_Un, + Unbox, + Throw, + Ldfld, + Ldflda, + Stfld, + Ldsfld, + Ldsflda, + Stsfld, + Stobj, + Conv_Ovf_I1_Un, + Conv_Ovf_I2_Un, + Conv_Ovf_I4_Un, + Conv_Ovf_I8_Un, + Conv_Ovf_U1_Un, + Conv_Ovf_U2_Un, + Conv_Ovf_U4_Un, + Conv_Ovf_U8_Un, + Conv_Ovf_I_Un, + Conv_Ovf_U_Un, + Box, + Newarr, + Ldlen, + Ldelema, + Ldelem_I1, + Ldelem_U1, + Ldelem_I2, + Ldelem_U2, + Ldelem_I4, + Ldelem_U4, + Ldelem_I8, + Ldelem_I, + Ldelem_R4, + Ldelem_R8, + Ldelem_Ref, + Stelem_I, + Stelem_I1, + Stelem_I2, + Stelem_I4, + Stelem_I8, + Stelem_R4, + Stelem_R8, + Stelem_Ref, + Ldelem_Any, + Stelem_Any, + Unbox_Any, + Conv_Ovf_I1, + Conv_Ovf_U1, + Conv_Ovf_I2, + Conv_Ovf_U2, + Conv_Ovf_I4, + Conv_Ovf_U4, + Conv_Ovf_I8, + Conv_Ovf_U8, + Refanyval, + Ckfinite, + Mkrefany, + Ldtoken, + Conv_U2, + Conv_U1, + Conv_I, + Conv_Ovf_I, + Conv_Ovf_U, + Add_Ovf, + Add_Ovf_Un, + Mul_Ovf, + Mul_Ovf_Un, + Sub_Ovf, + Sub_Ovf_Un, + Endfinally, + Leave, + __Leave_S, + __Stind_I, + Conv_U, + Arglist, + Ceq, + Cgt, + Cgt_Un, + Clt, + Clt_Un, + Ldftn, + Ldvirtftn, + __Ldarg, + __Ldarga, + __Starg, + Ldloc, + Ldloca, + Stloc, + Localloc, + Endfilter, + Unaligned, + Volatile, + Tail, + Initobj, + Constrained, + Cpblk, + Initblk, + No, + Rethrow, + Sizeof, + Refanytype, + Readonly, + + // Virtual codes - defined for convenience + Cne, + Cge, + Cge_Un, + Cle, + Cle_Un, + Ldexception, // Operand holds the CatchType for catch handler, null for filter + LogicNot, + LogicAnd, + LogicOr, + NullCoalescing, + InitArray, // Array Initializer + + /// <summary> + /// Defines a barrier between the parent expression and the argument expression that prevents combining them + /// </summary> + Wrap, + + // new Class { Prop = 1, Collection = { { 2, 3 }, {4, 5} }} + // is represented as: + // InitObject(newobj Class, + // CallSetter(Prop, InitializedObject, 1), + // InitCollection(CallGetter(Collection, InitializedObject))), + // Call(Add, InitializedObject, 2, 3), + // Call(Add, InitializedObject, 4, 5))) + InitObject, // Object initializer: first arg is newobj/defaultvalue, remaining args are the initializing statements + InitCollection, // Collection initializer: first arg is newobj/defaultvalue, remaining args are the initializing statements + InitializedObject, // Refers the the object being initialized (refers to first arg in parent InitObject or InitCollection instruction) + + TernaryOp, // ?: + LoopOrSwitchBreak, + LoopContinue, + Ldc_Decimal, + YieldBreak, + YieldReturn, + /// <summary> + /// Represents the 'default(T)' instruction. + /// </summary> + /// <remarks>Introduced by SimplifyLdObjAndStObj step</remarks> + DefaultValue, + /// <summary> + /// ILExpression with a single child: binary operator. + /// This expression means that the binary operator will also assign the new value to its left-hand side. + /// 'CompoundAssignment' must not be used for local variables, as inlining (and other) optimizations don't know that it modifies the variable. + /// </summary> + /// <remarks>Introduced by MakeCompoundAssignments step</remarks> + CompoundAssignment, + /// <summary> + /// Represents the post-increment operator. + /// The first argument is the address of the variable to increment (ldloca instruction). + /// The second arugment is the amount the variable is incremented by (ldc.i4 instruction) + /// </summary> + /// <remarks>Introduced by IntroducePostIncrement step</remarks> + PostIncrement, + PostIncrement_Ovf, // checked variant of PostIncrement + PostIncrement_Ovf_Un, // checked variant of PostIncrement, for unsigned integers + /// <summary>Calls the getter of a static property (or indexer), or of an instance property on 'base'</summary> + CallGetter, + /// <summary>Calls the getter of an instance property (or indexer)</summary> + CallvirtGetter, + /// <summary>Calls the setter of a static property (or indexer), or of an instance property on 'base'</summary> + /// <remarks>This allows us to represent "while ((SomeProperty = val) != null) {}"</remarks> + CallSetter, + /// <summary>Calls the setter of a instance property (or indexer)</summary> + CallvirtSetter, + /// <summary>Simulates getting the address of the argument instruction.</summary> + /// <remarks> + /// Used for postincrement for properties, and to represent the Address() method on multi-dimensional arrays. + /// Also used when inlining a method call on a value type: "stloc(v, ...); call(M, ldloca(v));" becomes "call(M, AddressOf(...))" + /// </remarks> + AddressOf, + /// <summary>Simulates getting the value of a lifted operator's nullable argument</summary> + /// <remarks> + /// For example "stloc(v1, ...); stloc(v2, ...); logicand(ceq(call(Nullable`1::GetValueOrDefault, ldloca(v1)), ldloc(v2)), callgetter(Nullable`1::get_HasValue, ldloca(v1)))" becomes "wrap(ceq(ValueOf(...), ...))" + /// </remarks> + ValueOf, + /// <summary>Simulates creating a new nullable value from a value type argument</summary> + /// <remarks> + /// For example "stloc(v1, ...); stloc(v2, ...); ternaryop(callgetter(Nullable`1::get_HasValue, ldloca(v1)), newobj(Nullable`1::.ctor, add(call(Nullable`1::GetValueOrDefault, ldloca(v1)), ldloc(v2))), defaultvalue(Nullable`1))" + /// becomes "NullableOf(add(valueof(...), ...))" + /// </remarks> + NullableOf, + /// <summary> + /// Declares parameters that are used in an expression tree. + /// The last child of this node is the call constructing the expression tree, all other children are the + /// assignments to the ParameterExpression variables. + /// </summary> + ExpressionTreeParameterDeclarations, + /// <summary> + /// C# 5 await + /// </summary> + Await + } + + public static class ILCodeUtil + { + public static string GetName(this ILCode code) + { + return code.ToString().ToLowerInvariant().TrimStart('_').Replace('_','.'); + } + + public static bool IsConditionalControlFlow(this ILCode code) + { + switch(code) { + case ILCode.__Brfalse_S: + case ILCode.__Brtrue_S: + case ILCode.__Beq_S: + case ILCode.__Bge_S: + case ILCode.__Bgt_S: + case ILCode.__Ble_S: + case ILCode.__Blt_S: + case ILCode.__Bne_Un_S: + case ILCode.__Bge_Un_S: + case ILCode.__Bgt_Un_S: + case ILCode.__Ble_Un_S: + case ILCode.__Blt_Un_S: + case ILCode.__Brfalse: + case ILCode.Brtrue: + case ILCode.__Beq: + case ILCode.__Bge: + case ILCode.__Bgt: + case ILCode.__Ble: + case ILCode.__Blt: + case ILCode.__Bne_Un: + case ILCode.__Bge_Un: + case ILCode.__Bgt_Un: + case ILCode.__Ble_Un: + case ILCode.__Blt_Un: + case ILCode.Switch: + return true; + default: + return false; + } + } + + public static bool IsUnconditionalControlFlow(this ILCode code) + { + switch(code) { + case ILCode.Br: + case ILCode.__Br_S: + case ILCode.Leave: + case ILCode.__Leave_S: + case ILCode.Ret: + case ILCode.Endfilter: + case ILCode.Endfinally: + case ILCode.Throw: + case ILCode.Rethrow: + case ILCode.LoopContinue: + case ILCode.LoopOrSwitchBreak: + case ILCode.YieldBreak: + return true; + default: + return false; + } + } + + public static void ExpandMacro(ref ILCode code, ref object operand, MethodBody methodBody) + { + switch (code) { + case ILCode.__Ldarg_0: code = ILCode.__Ldarg; operand = methodBody.GetParameter(0); break; + case ILCode.__Ldarg_1: code = ILCode.__Ldarg; operand = methodBody.GetParameter(1); break; + case ILCode.__Ldarg_2: code = ILCode.__Ldarg; operand = methodBody.GetParameter(2); break; + case ILCode.__Ldarg_3: code = ILCode.__Ldarg; operand = methodBody.GetParameter(3); break; + case ILCode.__Ldloc_0: code = ILCode.Ldloc; operand = methodBody.Variables[0]; break; + case ILCode.__Ldloc_1: code = ILCode.Ldloc; operand = methodBody.Variables[1]; break; + case ILCode.__Ldloc_2: code = ILCode.Ldloc; operand = methodBody.Variables[2]; break; + case ILCode.__Ldloc_3: code = ILCode.Ldloc; operand = methodBody.Variables[3]; break; + case ILCode.__Stloc_0: code = ILCode.Stloc; operand = methodBody.Variables[0]; break; + case ILCode.__Stloc_1: code = ILCode.Stloc; operand = methodBody.Variables[1]; break; + case ILCode.__Stloc_2: code = ILCode.Stloc; operand = methodBody.Variables[2]; break; + case ILCode.__Stloc_3: code = ILCode.Stloc; operand = methodBody.Variables[3]; break; + case ILCode.__Ldarg_S: code = ILCode.__Ldarg; break; + case ILCode.__Ldarga_S: code = ILCode.__Ldarga; break; + case ILCode.__Starg_S: code = ILCode.__Starg; break; + case ILCode.__Ldloc_S: code = ILCode.Ldloc; break; + case ILCode.__Ldloca_S: code = ILCode.Ldloca; break; + case ILCode.__Stloc_S: code = ILCode.Stloc; break; + case ILCode.__Ldc_I4_M1: code = ILCode.Ldc_I4; operand = -1; break; + case ILCode.__Ldc_I4_0: code = ILCode.Ldc_I4; operand = 0; break; + case ILCode.__Ldc_I4_1: code = ILCode.Ldc_I4; operand = 1; break; + case ILCode.__Ldc_I4_2: code = ILCode.Ldc_I4; operand = 2; break; + case ILCode.__Ldc_I4_3: code = ILCode.Ldc_I4; operand = 3; break; + case ILCode.__Ldc_I4_4: code = ILCode.Ldc_I4; operand = 4; break; + case ILCode.__Ldc_I4_5: code = ILCode.Ldc_I4; operand = 5; break; + case ILCode.__Ldc_I4_6: code = ILCode.Ldc_I4; operand = 6; break; + case ILCode.__Ldc_I4_7: code = ILCode.Ldc_I4; operand = 7; break; + case ILCode.__Ldc_I4_8: code = ILCode.Ldc_I4; operand = 8; break; + case ILCode.__Ldc_I4_S: code = ILCode.Ldc_I4; operand = (int) (sbyte) operand; break; + case ILCode.__Br_S: code = ILCode.Br; break; + case ILCode.__Brfalse_S: code = ILCode.__Brfalse; break; + case ILCode.__Brtrue_S: code = ILCode.Brtrue; break; + case ILCode.__Beq_S: code = ILCode.__Beq; break; + case ILCode.__Bge_S: code = ILCode.__Bge; break; + case ILCode.__Bgt_S: code = ILCode.__Bgt; break; + case ILCode.__Ble_S: code = ILCode.__Ble; break; + case ILCode.__Blt_S: code = ILCode.__Blt; break; + case ILCode.__Bne_Un_S: code = ILCode.__Bne_Un; break; + case ILCode.__Bge_Un_S: code = ILCode.__Bge_Un; break; + case ILCode.__Bgt_Un_S: code = ILCode.__Bgt_Un; break; + case ILCode.__Ble_Un_S: code = ILCode.__Ble_Un; break; + case ILCode.__Blt_Un_S: code = ILCode.__Blt_Un; break; + case ILCode.__Leave_S: code = ILCode.Leave; break; + case ILCode.__Ldind_I: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.IntPtr; break; + case ILCode.__Ldind_I1: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.SByte; break; + case ILCode.__Ldind_I2: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.Int16; break; + case ILCode.__Ldind_I4: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.Int32; break; + case ILCode.__Ldind_I8: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.Int64; break; + case ILCode.__Ldind_U1: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.Byte; break; + case ILCode.__Ldind_U2: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.UInt16; break; + case ILCode.__Ldind_U4: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.UInt32; break; + case ILCode.__Ldind_R4: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.Single; break; + case ILCode.__Ldind_R8: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.Double; break; + case ILCode.__Stind_I: code = ILCode.Stobj; operand = methodBody.Method.Module.TypeSystem.IntPtr; break; + case ILCode.__Stind_I1: code = ILCode.Stobj; operand = methodBody.Method.Module.TypeSystem.Byte; break; + case ILCode.__Stind_I2: code = ILCode.Stobj; operand = methodBody.Method.Module.TypeSystem.Int16; break; + case ILCode.__Stind_I4: code = ILCode.Stobj; operand = methodBody.Method.Module.TypeSystem.Int32; break; + case ILCode.__Stind_I8: code = ILCode.Stobj; operand = methodBody.Method.Module.TypeSystem.Int64; break; + case ILCode.__Stind_R4: code = ILCode.Stobj; operand = methodBody.Method.Module.TypeSystem.Single; break; + case ILCode.__Stind_R8: code = ILCode.Stobj; operand = methodBody.Method.Module.TypeSystem.Double; break; + } + } + + public static ParameterDefinition GetParameter (this MethodBody self, int index) + { + var method = self.Method; + + if (method.HasThis) { + if (index == 0) + return self.ThisParameter; + + index--; + } + + var parameters = method.Parameters; + + if (index < 0 || index >= parameters.Count) + return null; + + return parameters [index]; + } + } +} diff --git a/ICSharpCode.Decompiler/ILAst/ILInlining.cs b/ICSharpCode.Decompiler/ILAst/ILInlining.cs new file mode 100644 index 00000000..11140ef0 --- /dev/null +++ b/ICSharpCode.Decompiler/ILAst/ILInlining.cs @@ -0,0 +1,524 @@ +// 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 System.Linq; +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.ILAst +{ + /// <summary> + /// Performs inlining transformations. + /// </summary> + public class ILInlining + { + readonly ILBlock method; + internal Dictionary<ILVariable, int> numStloc = new Dictionary<ILVariable, int>(); + internal Dictionary<ILVariable, int> numLdloc = new Dictionary<ILVariable, int>(); + internal Dictionary<ILVariable, int> numLdloca = new Dictionary<ILVariable, int>(); + + public ILInlining(ILBlock method) + { + this.method = method; + AnalyzeMethod(); + } + + void AnalyzeMethod() + { + numStloc.Clear(); + numLdloc.Clear(); + numLdloca.Clear(); + + // Analyse the whole method + AnalyzeNode(method); + } + + /// <summary> + /// For each variable reference, adds <paramref name="direction"/> to the num* dicts. + /// Direction will be 1 for analysis, and -1 when removing a node from analysis. + /// </summary> + void AnalyzeNode(ILNode node, int direction = 1) + { + ILExpression expr = node as ILExpression; + if (expr != null) { + ILVariable locVar = expr.Operand as ILVariable; + if (locVar != null) { + if (expr.Code == ILCode.Stloc) { + numStloc[locVar] = numStloc.GetOrDefault(locVar) + direction; + } else if (expr.Code == ILCode.Ldloc) { + numLdloc[locVar] = numLdloc.GetOrDefault(locVar) + direction; + } else if (expr.Code == ILCode.Ldloca) { + numLdloca[locVar] = numLdloca.GetOrDefault(locVar) + direction; + } else { + throw new NotSupportedException(expr.Code.ToString()); + } + } + foreach (ILExpression child in expr.Arguments) + AnalyzeNode(child, direction); + } else { + var catchBlock = node as ILTryCatchBlock.CatchBlock; + if (catchBlock != null && catchBlock.ExceptionVariable != null) { + numStloc[catchBlock.ExceptionVariable] = numStloc.GetOrDefault(catchBlock.ExceptionVariable) + direction; + } + + foreach (ILNode child in node.GetChildren()) + AnalyzeNode(child, direction); + } + } + + public bool InlineAllVariables() + { + bool modified = false; + ILInlining i = new ILInlining(method); + foreach (ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) + modified |= i.InlineAllInBlock(block); + return modified; + } + + public bool InlineAllInBlock(ILBlock block) + { + bool modified = false; + List<ILNode> body = block.Body; + if (block is ILTryCatchBlock.CatchBlock && body.Count > 1) { + ILVariable v = ((ILTryCatchBlock.CatchBlock)block).ExceptionVariable; + if (v != null && v.IsGenerated) { + if (numLdloca.GetOrDefault(v) == 0 && numStloc.GetOrDefault(v) == 1 && numLdloc.GetOrDefault(v) == 1) { + ILVariable v2; + ILExpression ldException; + if (body[0].Match(ILCode.Stloc, out v2, out ldException) && ldException.MatchLdloc(v)) { + body.RemoveAt(0); + ((ILTryCatchBlock.CatchBlock)block).ExceptionVariable = v2; + modified = true; + } + } + } + } + for(int i = 0; i < body.Count - 1;) { + ILVariable locVar; + ILExpression expr; + if (body[i].Match(ILCode.Stloc, out locVar, out expr) && InlineOneIfPossible(block.Body, i, aggressive: false)) { + modified = true; + i = Math.Max(0, i - 1); // Go back one step + } else { + i++; + } + } + foreach(ILBasicBlock bb in body.OfType<ILBasicBlock>()) { + modified |= InlineAllInBasicBlock(bb); + } + return modified; + } + + public bool InlineAllInBasicBlock(ILBasicBlock bb) + { + bool modified = false; + List<ILNode> body = bb.Body; + for(int i = 0; i < body.Count;) { + ILVariable locVar; + ILExpression expr; + if (body[i].Match(ILCode.Stloc, out locVar, out expr) && InlineOneIfPossible(bb.Body, i, aggressive: false)) { + modified = true; + i = Math.Max(0, i - 1); // Go back one step + } else { + i++; + } + } + return modified; + } + + /// <summary> + /// Inlines instructions before pos into block.Body[pos]. + /// </summary> + /// <returns>The number of instructions that were inlined.</returns> + public int InlineInto(List<ILNode> body, int pos, bool aggressive) + { + if (pos >= body.Count) + return 0; + int count = 0; + while (--pos >= 0) { + ILExpression expr = body[pos] as ILExpression; + if (expr == null || expr.Code != ILCode.Stloc) + break; + if (InlineOneIfPossible(body, pos, aggressive)) + count++; + else + break; + } + return count; + } + + /// <summary> + /// Aggressively inlines the stloc instruction at block.Body[pos] into the next instruction, if possible. + /// If inlining was possible; we will continue to inline (non-aggressively) into the the combined instruction. + /// </summary> + /// <remarks> + /// After the operation, pos will point to the new combined instruction. + /// </remarks> + public bool InlineIfPossible(List<ILNode> body, ref int pos) + { + if (InlineOneIfPossible(body, pos, true)) { + pos -= InlineInto(body, pos, false); + return true; + } + return false; + } + + /// <summary> + /// Inlines the stloc instruction at block.Body[pos] into the next instruction, if possible. + /// </summary> + public bool InlineOneIfPossible(List<ILNode> body, int pos, bool aggressive) + { + ILVariable v; + ILExpression inlinedExpression; + if (body[pos].Match(ILCode.Stloc, out v, out inlinedExpression) && !v.IsPinned) { + if (InlineIfPossible(v, inlinedExpression, body.ElementAtOrDefault(pos+1), aggressive)) { + // Assign the ranges of the stloc instruction: + inlinedExpression.ILRanges.AddRange(((ILExpression)body[pos]).ILRanges); + // Remove the stloc instruction: + body.RemoveAt(pos); + return true; + } else if (numLdloc.GetOrDefault(v) == 0 && numLdloca.GetOrDefault(v) == 0) { + // The variable is never loaded + if (inlinedExpression.HasNoSideEffects()) { + // Remove completely + AnalyzeNode(body[pos], -1); + body.RemoveAt(pos); + return true; + } else if (inlinedExpression.CanBeExpressionStatement() && v.IsGenerated) { + // Assign the ranges of the stloc instruction: + inlinedExpression.ILRanges.AddRange(((ILExpression)body[pos]).ILRanges); + // Remove the stloc, but keep the inner expression + body[pos] = inlinedExpression; + return true; + } + } + } + return false; + } + + /// <summary> + /// Inlines 'expr' into 'next', if possible. + /// </summary> + bool InlineIfPossible(ILVariable v, ILExpression inlinedExpression, ILNode next, bool aggressive) + { + // ensure the variable is accessed only a single time + if (numStloc.GetOrDefault(v) != 1) + return false; + int ldloc = numLdloc.GetOrDefault(v); + if (ldloc > 1 || ldloc + numLdloca.GetOrDefault(v) != 1) + return false; + + if (next is ILCondition) + next = ((ILCondition)next).Condition; + else if (next is ILWhileLoop) + next = ((ILWhileLoop)next).Condition; + + ILExpression parent; + int pos; + if (FindLoadInNext(next as ILExpression, v, inlinedExpression, out parent, out pos) == true) { + if (ldloc == 0) { + if (!IsGeneratedValueTypeTemporary((ILExpression)next, parent, pos, v, inlinedExpression)) + return false; + } else { + if (!aggressive && !v.IsGenerated && !NonAggressiveInlineInto((ILExpression)next, parent, inlinedExpression)) + return false; + } + + // Assign the ranges of the ldloc instruction: + inlinedExpression.ILRanges.AddRange(parent.Arguments[pos].ILRanges); + + if (ldloc == 0) { + // it was an ldloca instruction, so we need to use the pseudo-opcode 'addressof' so that the types + // comes out correctly + parent.Arguments[pos] = new ILExpression(ILCode.AddressOf, null, inlinedExpression); + } else { + parent.Arguments[pos] = inlinedExpression; + } + return true; + } + return false; + } + + /// <summary> + /// Is this a temporary variable generated by the C# compiler for instance method calls on value type values + /// </summary> + /// <param name="next">The next top-level expression</param> + /// <param name="parent">The direct parent of the load within 'next'</param> + /// <param name="pos">Index of the load within 'parent'</param> + /// <param name="v">The variable being inlined.</param> + /// <param name="inlinedExpression">The expression being inlined</param> + bool IsGeneratedValueTypeTemporary(ILExpression next, ILExpression parent, int pos, ILVariable v, ILExpression inlinedExpression) + { + if (pos == 0 && v.Type != null && v.Type.IsValueType) { + // Inlining a value type variable is allowed only if the resulting code will maintain the semantics + // that the method is operating on a copy. + // Thus, we have to disallow inlining of other locals, fields, array elements, dereferenced pointers + switch (inlinedExpression.Code) { + case ILCode.Ldloc: + case ILCode.Stloc: + case ILCode.CompoundAssignment: + case ILCode.Ldelem_Any: + case ILCode.Ldelem_I: + case ILCode.Ldelem_I1: + case ILCode.Ldelem_I2: + case ILCode.Ldelem_I4: + case ILCode.Ldelem_I8: + case ILCode.Ldelem_R4: + case ILCode.Ldelem_R8: + case ILCode.Ldelem_Ref: + case ILCode.Ldelem_U1: + case ILCode.Ldelem_U2: + case ILCode.Ldelem_U4: + case ILCode.Ldobj: + case ILCode.Ldind_Ref: + return false; + case ILCode.Ldfld: + case ILCode.Stfld: + case ILCode.Ldsfld: + case ILCode.Stsfld: + // allow inlining field access only if it's a readonly field + FieldDefinition f = ((FieldReference)inlinedExpression.Operand).Resolve(); + if (!(f != null && f.IsInitOnly)) + return false; + break; + case ILCode.Call: + case ILCode.CallGetter: + // inlining runs both before and after IntroducePropertyAccessInstructions, + // so we have to handle both 'call' and 'callgetter' + MethodReference mr = (MethodReference)inlinedExpression.Operand; + // ensure that it's not an multi-dimensional array getter + if (mr.DeclaringType is ArrayType) + return false; + goto case ILCode.Callvirt; + case ILCode.Callvirt: + case ILCode.CallvirtGetter: + // don't inline foreach loop variables: + mr = (MethodReference)inlinedExpression.Operand; + if (mr.Name == "get_Current" && mr.HasThis) + return false; + break; + case ILCode.Castclass: + case ILCode.Unbox_Any: + // These are valid, but might occur as part of a foreach loop variable. + ILExpression arg = inlinedExpression.Arguments[0]; + if (arg.Code == ILCode.CallGetter || arg.Code == ILCode.CallvirtGetter || arg.Code == ILCode.Call || arg.Code == ILCode.Callvirt) { + mr = (MethodReference)arg.Operand; + if (mr.Name == "get_Current" && mr.HasThis) + return false; // looks like a foreach loop variable, so don't inline it + } + break; + } + + // inline the compiler-generated variable that are used when accessing a member on a value type: + switch (parent.Code) { + case ILCode.Call: + case ILCode.CallGetter: + case ILCode.CallSetter: + case ILCode.Callvirt: + case ILCode.CallvirtGetter: + case ILCode.CallvirtSetter: + MethodReference mr = (MethodReference)parent.Operand; + return mr.HasThis; + case ILCode.Stfld: + case ILCode.Ldfld: + case ILCode.Ldflda: + case ILCode.Await: + return true; + } + } + return false; + } + + /// <summary> + /// Determines whether a variable should be inlined in non-aggressive mode, even though it is not a generated variable. + /// </summary> + /// <param name="next">The next top-level expression</param> + /// <param name="parent">The direct parent of the load within 'next'</param> + /// <param name="inlinedExpression">The expression being inlined</param> + bool NonAggressiveInlineInto(ILExpression next, ILExpression parent, ILExpression inlinedExpression) + { + if (inlinedExpression.Code == ILCode.DefaultValue) + return true; + + switch (next.Code) { + case ILCode.Ret: + case ILCode.Brtrue: + return parent == next; + case ILCode.Switch: + return parent == next || (parent.Code == ILCode.Sub && parent == next.Arguments[0]); + default: + return false; + } + } + + /// <summary> + /// Gets whether 'expressionBeingMoved' can be inlined into 'expr'. + /// </summary> + public bool CanInlineInto(ILExpression expr, ILVariable v, ILExpression expressionBeingMoved) + { + ILExpression parent; + int pos; + return FindLoadInNext(expr, v, expressionBeingMoved, out parent, out pos) == true; + } + + /// <summary> + /// Finds the position to inline to. + /// </summary> + /// <returns>true = found; false = cannot continue search; null = not found</returns> + bool? FindLoadInNext(ILExpression expr, ILVariable v, ILExpression expressionBeingMoved, out ILExpression parent, out int pos) + { + parent = null; + pos = 0; + if (expr == null) + return false; + for (int i = 0; i < expr.Arguments.Count; i++) { + // Stop when seeing an opcode that does not guarantee that its operands will be evaluated. + // Inlining in that case might result in the inlined expresion not being evaluted. + if (i == 1 && (expr.Code == ILCode.LogicAnd || expr.Code == ILCode.LogicOr || expr.Code == ILCode.TernaryOp || expr.Code == ILCode.NullCoalescing)) + return false; + + ILExpression arg = expr.Arguments[i]; + + if ((arg.Code == ILCode.Ldloc || arg.Code == ILCode.Ldloca) && arg.Operand == v) { + parent = expr; + pos = i; + return true; + } + bool? r = FindLoadInNext(arg, v, expressionBeingMoved, out parent, out pos); + if (r != null) + return r; + } + if (IsSafeForInlineOver(expr, expressionBeingMoved)) + return null; // continue searching + else + return false; // abort, inlining not possible + } + + /// <summary> + /// Determines whether it is safe to move 'expressionBeingMoved' past 'expr' + /// </summary> + bool IsSafeForInlineOver(ILExpression expr, ILExpression expressionBeingMoved) + { + switch (expr.Code) { + case ILCode.Ldloc: + ILVariable loadedVar = (ILVariable)expr.Operand; + if (numLdloca.GetOrDefault(loadedVar) != 0) { + // abort, inlining is not possible + return false; + } + foreach (ILExpression potentialStore in expressionBeingMoved.GetSelfAndChildrenRecursive<ILExpression>()) { + if (potentialStore.Code == ILCode.Stloc && potentialStore.Operand == loadedVar) + return false; + } + // the expression is loading a non-forbidden variable + return true; + case ILCode.Ldloca: + case ILCode.Ldflda: + case ILCode.Ldsflda: + case ILCode.Ldelema: + case ILCode.AddressOf: + case ILCode.ValueOf: + case ILCode.NullableOf: + // address-loading instructions are safe if their arguments are safe + foreach (ILExpression arg in expr.Arguments) { + if (!IsSafeForInlineOver(arg, expressionBeingMoved)) + return false; + } + return true; + default: + // instructions with no side-effects are safe (except for Ldloc and Ldloca which are handled separately) + return expr.HasNoSideEffects(); + } + } + + /// <summary> + /// Runs a very simple form of copy propagation. + /// Copy propagation is used in two cases: + /// 1) assignments from arguments to local variables + /// If the target variable is assigned to only once (so always is that argument) and the argument is never changed (no ldarga/starg), + /// then we can replace the variable with the argument. + /// 2) assignments of address-loading instructions to local variables + /// </summary> + public void CopyPropagation() + { + foreach (ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) { + for (int i = 0; i < block.Body.Count; i++) { + ILVariable v; + ILExpression copiedExpr; + if (block.Body[i].Match(ILCode.Stloc, out v, out copiedExpr) + && !v.IsParameter && numStloc.GetOrDefault(v) == 1 && numLdloca.GetOrDefault(v) == 0 + && CanPerformCopyPropagation(copiedExpr, v)) + { + // un-inline the arguments of the ldArg instruction + ILVariable[] uninlinedArgs = new ILVariable[copiedExpr.Arguments.Count]; + for (int j = 0; j < uninlinedArgs.Length; j++) { + uninlinedArgs[j] = new ILVariable { IsGenerated = true, Name = v.Name + "_cp_" + j }; + block.Body.Insert(i++, new ILExpression(ILCode.Stloc, uninlinedArgs[j], copiedExpr.Arguments[j])); + } + + // perform copy propagation: + foreach (var expr in method.GetSelfAndChildrenRecursive<ILExpression>()) { + if (expr.Code == ILCode.Ldloc && expr.Operand == v) { + expr.Code = copiedExpr.Code; + expr.Operand = copiedExpr.Operand; + for (int j = 0; j < uninlinedArgs.Length; j++) { + expr.Arguments.Add(new ILExpression(ILCode.Ldloc, uninlinedArgs[j])); + } + } + } + + block.Body.RemoveAt(i); + if (uninlinedArgs.Length > 0) { + // if we un-inlined stuff; we need to update the usage counters + AnalyzeMethod(); + } + InlineInto(block.Body, i, aggressive: false); // maybe inlining gets possible after the removal of block.Body[i] + i -= uninlinedArgs.Length + 1; + } + } + } + } + + bool CanPerformCopyPropagation(ILExpression expr, ILVariable copyVariable) + { + switch (expr.Code) { + case ILCode.Ldloca: + case ILCode.Ldelema: + case ILCode.Ldflda: + case ILCode.Ldsflda: + // All address-loading instructions always return the same value for a given operand/argument combination, + // so they can be safely copied. + return true; + case ILCode.Ldloc: + ILVariable v = (ILVariable)expr.Operand; + if (v.IsParameter) { + // Parameters can be copied only if they aren't assigned to (directly or indirectly via ldarga) + return numLdloca.GetOrDefault(v) == 0 && numStloc.GetOrDefault(v) == 0; + } else { + // Variables are be copied only if both they and the target copy variable are generated, + // and if the variable has only a single assignment + return v.IsGenerated && copyVariable.IsGenerated && numLdloca.GetOrDefault(v) == 0 && numStloc.GetOrDefault(v) == 1; + } + default: + return false; + } + } + } +} diff --git a/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs b/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs new file mode 100644 index 00000000..4a4a8138 --- /dev/null +++ b/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs @@ -0,0 +1,543 @@ +// 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 System.Linq; +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.ILAst +{ + /// <summary> + /// IL AST transformation that introduces array, object and collection initializers. + /// </summary> + partial class ILAstOptimizer + { + #region Array Initializers + bool TransformArrayInitializers(List<ILNode> body, ILExpression expr, int pos) + { + ILVariable v, v3; + ILExpression newarrExpr; + TypeReference elementType; + ILExpression lengthExpr; + int arrayLength; + if (expr.Match(ILCode.Stloc, out v, out newarrExpr) && + newarrExpr.Match(ILCode.Newarr, out elementType, out lengthExpr) && + lengthExpr.Match(ILCode.Ldc_I4, out arrayLength) && + arrayLength > 0) { + ILExpression[] newArr; + int initArrayPos; + if (ForwardScanInitializeArrayRuntimeHelper(body, pos + 1, v, elementType, arrayLength, out newArr, out initArrayPos)) { + var arrayType = new ArrayType(elementType, 1); + arrayType.Dimensions[0] = new ArrayDimension(0, arrayLength); + body[pos] = new ILExpression(ILCode.Stloc, v, new ILExpression(ILCode.InitArray, arrayType, newArr)); + body.RemoveAt(initArrayPos); + } + // Put in a limit so that we don't consume too much memory if the code allocates a huge array + // and populates it extremely sparsly. However, 255 "null" elements in a row actually occur in the Mono C# compiler! + const int maxConsecutiveDefaultValueExpressions = 300; + List<ILExpression> operands = new List<ILExpression>(); + int numberOfInstructionsToRemove = 0; + for (int j = pos + 1; j < body.Count; j++) { + ILExpression nextExpr = body[j] as ILExpression; + int arrayPos; + if (nextExpr != null && + nextExpr.Code.IsStoreToArray() && + nextExpr.Arguments[0].Match(ILCode.Ldloc, out v3) && + v == v3 && + nextExpr.Arguments[1].Match(ILCode.Ldc_I4, out arrayPos) && + arrayPos >= operands.Count && + arrayPos <= operands.Count + maxConsecutiveDefaultValueExpressions && + !nextExpr.Arguments[2].ContainsReferenceTo(v3)) + { + while (operands.Count < arrayPos) + operands.Add(new ILExpression(ILCode.DefaultValue, elementType)); + operands.Add(nextExpr.Arguments[2]); + numberOfInstructionsToRemove++; + } else { + break; + } + } + if (operands.Count == arrayLength) { + var arrayType = new ArrayType(elementType, 1); + arrayType.Dimensions[0] = new ArrayDimension(0, arrayLength); + expr.Arguments[0] = new ILExpression(ILCode.InitArray, arrayType, operands); + body.RemoveRange(pos + 1, numberOfInstructionsToRemove); + + new ILInlining(method).InlineIfPossible(body, ref pos); + return true; + } + } + return false; + } + + bool TransformMultidimensionalArrayInitializers(List<ILNode> body, ILExpression expr, int pos) + { + ILVariable v; + ILExpression newarrExpr; + MethodReference ctor; + List<ILExpression> ctorArgs; + ArrayType arrayType; + if (expr.Match(ILCode.Stloc, out v, out newarrExpr) && + newarrExpr.Match(ILCode.Newobj, out ctor, out ctorArgs) && + (arrayType = (ctor.DeclaringType as ArrayType)) != null && + arrayType.Rank == ctorArgs.Count) { + // Clone the type, so we can muck about with the Dimensions + arrayType = new ArrayType(arrayType.ElementType, arrayType.Rank); + var arrayLengths = new int[arrayType.Rank]; + for (int i = 0; i < arrayType.Rank; i++) { + if (!ctorArgs[i].Match(ILCode.Ldc_I4, out arrayLengths[i])) return false; + if (arrayLengths[i] <= 0) return false; + arrayType.Dimensions[i] = new ArrayDimension(0, arrayLengths[i]); + } + + var totalElements = arrayLengths.Aggregate(1, (t, l) => t * l); + ILExpression[] newArr; + int initArrayPos; + if (ForwardScanInitializeArrayRuntimeHelper(body, pos + 1, v, arrayType, totalElements, out newArr, out initArrayPos)) { + body[pos] = new ILExpression(ILCode.Stloc, v, new ILExpression(ILCode.InitArray, arrayType, newArr)); + body.RemoveAt(initArrayPos); + return true; + } + } + return false; + } + + bool ForwardScanInitializeArrayRuntimeHelper(List<ILNode> body, int pos, ILVariable array, TypeReference arrayType, int arrayLength, out ILExpression[] values, out int foundPos) + { + ILVariable v2; + MethodReference methodRef; + ILExpression methodArg1; + ILExpression methodArg2; + FieldReference fieldRef; + if (body.ElementAtOrDefault(pos).Match(ILCode.Call, out methodRef, out methodArg1, out methodArg2) && + methodRef.DeclaringType.FullName == "System.Runtime.CompilerServices.RuntimeHelpers" && + methodRef.Name == "InitializeArray" && + methodArg1.Match(ILCode.Ldloc, out v2) && + array == v2 && + methodArg2.Match(ILCode.Ldtoken, out fieldRef)) + { + FieldDefinition fieldDef = fieldRef.ResolveWithinSameModule(); + if (fieldDef != null && fieldDef.InitialValue != null) { + ILExpression[] newArr = new ILExpression[arrayLength]; + if (DecodeArrayInitializer(arrayType.GetElementType(), fieldDef.InitialValue, newArr)) + { + values = newArr; + foundPos = pos; + return true; + } + } + } + values = null; + foundPos = -1; + return false; + } + + static bool DecodeArrayInitializer(TypeReference elementTypeRef, byte[] initialValue, ILExpression[] output) + { + TypeCode elementType = TypeAnalysis.GetTypeCode(elementTypeRef); + switch (elementType) { + case TypeCode.Boolean: + case TypeCode.Byte: + return DecodeArrayInitializer(initialValue, output, elementType, (d, i) => (int)d[i]); + case TypeCode.SByte: + return DecodeArrayInitializer(initialValue, output, elementType, (d, i) => (int)unchecked((sbyte)d[i])); + case TypeCode.Int16: + return DecodeArrayInitializer(initialValue, output, elementType, (d, i) => (int)BitConverter.ToInt16(d, i)); + case TypeCode.Char: + case TypeCode.UInt16: + return DecodeArrayInitializer(initialValue, output, elementType, (d, i) => (int)BitConverter.ToUInt16(d, i)); + case TypeCode.Int32: + case TypeCode.UInt32: + return DecodeArrayInitializer(initialValue, output, elementType, BitConverter.ToInt32); + case TypeCode.Int64: + case TypeCode.UInt64: + return DecodeArrayInitializer(initialValue, output, elementType, BitConverter.ToInt64); + case TypeCode.Single: + return DecodeArrayInitializer(initialValue, output, elementType, BitConverter.ToSingle); + case TypeCode.Double: + return DecodeArrayInitializer(initialValue, output, elementType, BitConverter.ToDouble); + case TypeCode.Object: + var typeDef = elementTypeRef.ResolveWithinSameModule(); + if (typeDef != null && typeDef.IsEnum) + return DecodeArrayInitializer(typeDef.GetEnumUnderlyingType(), initialValue, output); + + return false; + default: + return false; + } + } + + static bool DecodeArrayInitializer<T>(byte[] initialValue, ILExpression[] output, TypeCode elementType, Func<byte[], int, T> decoder) + { + int elementSize = ElementSizeOf(elementType); + if (initialValue.Length < (output.Length * elementSize)) + return false; + + ILCode code = LoadCodeFor(elementType); + for (int i = 0; i < output.Length; i++) + output[i] = new ILExpression(code, decoder(initialValue, i * elementSize)); + + return true; + } + + static ILCode LoadCodeFor(TypeCode elementType) + { + switch (elementType) { + case TypeCode.Boolean: + case TypeCode.Byte: + case TypeCode.SByte: + case TypeCode.Char: + case TypeCode.Int16: + case TypeCode.UInt16: + case TypeCode.Int32: + case TypeCode.UInt32: + return ILCode.Ldc_I4; + case TypeCode.Int64: + case TypeCode.UInt64: + return ILCode.Ldc_I8; + case TypeCode.Single: + return ILCode.Ldc_R4; + case TypeCode.Double: + return ILCode.Ldc_R8; + default: + throw new ArgumentOutOfRangeException("elementType"); + } + } + + static int ElementSizeOf(TypeCode elementType) + { + switch (elementType) { + case TypeCode.Boolean: + case TypeCode.Byte: + case TypeCode.SByte: + return 1; + case TypeCode.Char: + case TypeCode.Int16: + case TypeCode.UInt16: + return 2; + case TypeCode.Int32: + case TypeCode.UInt32: + case TypeCode.Single: + return 4; + case TypeCode.Int64: + case TypeCode.UInt64: + case TypeCode.Double: + return 8; + default: + throw new ArgumentOutOfRangeException("elementType"); + } + } + #endregion + + /// <summary> + /// Handles both object and collection initializers. + /// </summary> + bool TransformObjectInitializers(List<ILNode> body, ILExpression expr, int pos) + { + if (!context.Settings.ObjectOrCollectionInitializers) + return false; + + Debug.Assert(body[pos] == expr); // should be called for top-level expressions only + ILVariable v; + ILExpression newObjExpr; + TypeReference newObjType; + bool isValueType; + MethodReference ctor; + List<ILExpression> ctorArgs; + if (expr.Match(ILCode.Stloc, out v, out newObjExpr)) { + if (newObjExpr.Match(ILCode.Newobj, out ctor, out ctorArgs)) { + // v = newObj(ctor, ctorArgs) + newObjType = ctor.DeclaringType; + isValueType = false; + } else if (newObjExpr.Match(ILCode.DefaultValue, out newObjType)) { + // v = defaultvalue(type) + isValueType = true; + } else { + return false; + } + } else if (expr.Match(ILCode.Call, out ctor, out ctorArgs)) { + // call(SomeStruct::.ctor, ldloca(v), remainingArgs) + if (ctorArgs.Count > 0 && ctorArgs[0].Match(ILCode.Ldloca, out v)) { + isValueType = true; + newObjType = ctor.DeclaringType; + ctorArgs = new List<ILExpression>(ctorArgs); + ctorArgs.RemoveAt(0); + newObjExpr = new ILExpression(ILCode.Newobj, ctor, ctorArgs); + } else { + return false; + } + } else { + return false; + } + if (newObjType.IsValueType != isValueType) + return false; + + int originalPos = pos; + + // don't use object initializer syntax for closures + if (Ast.Transforms.DelegateConstruction.IsPotentialClosure(context, newObjType.ResolveWithinSameModule())) + return false; + + ILExpression initializer = ParseObjectInitializer(body, ref pos, v, newObjExpr, IsCollectionType(newObjType), isValueType); + + if (initializer.Arguments.Count == 1) // only newobj argument, no initializer elements + return false; + int totalElementCount = pos - originalPos - 1; // totalElementCount: includes elements from nested collections + Debug.Assert(totalElementCount >= initializer.Arguments.Count - 1); + + // Verify that we can inline 'v' into the next instruction: + + if (pos >= body.Count) + return false; // reached end of block, but there should be another instruction which consumes the initialized object + + ILInlining inlining = new ILInlining(method); + if (isValueType) { + // one ldloc for the use of the initialized object + if (inlining.numLdloc.GetOrDefault(v) != 1) + return false; + // one ldloca for each initializer argument, and also for the ctor call (if it exists) + if (inlining.numLdloca.GetOrDefault(v) != totalElementCount + (expr.Code == ILCode.Call ? 1 : 0)) + return false; + // one stloc for the initial store (if no ctor call was used) + if (inlining.numStloc.GetOrDefault(v) != (expr.Code == ILCode.Call ? 0 : 1)) + return false; + } else { + // one ldloc for each initializer argument, and another ldloc for the use of the initialized object + if (inlining.numLdloc.GetOrDefault(v) != totalElementCount + 1) + return false; + if (!(inlining.numStloc.GetOrDefault(v) == 1 && inlining.numLdloca.GetOrDefault(v) == 0)) + return false; + } + ILExpression nextExpr = body[pos] as ILExpression; + if (!inlining.CanInlineInto(nextExpr, v, initializer)) + return false; + + if (expr.Code == ILCode.Stloc) { + expr.Arguments[0] = initializer; + } else { + Debug.Assert(expr.Code == ILCode.Call); + expr.Code = ILCode.Stloc; + expr.Operand = v; + expr.Arguments.Clear(); + expr.Arguments.Add(initializer); + } + // remove all the instructions that were pulled into the initializer + body.RemoveRange(originalPos + 1, pos - originalPos - 1); + + // now that we know that it's an object initializer, change all the first arguments to 'InitializedObject' + ChangeFirstArgumentToInitializedObject(initializer); + + inlining = new ILInlining(method); + inlining.InlineIfPossible(body, ref originalPos); + + return true; + } + + /// <summary> + /// Gets whether the type supports collection initializers. + /// </summary> + static bool IsCollectionType(TypeReference tr) + { + if (tr == null) + return false; + TypeDefinition td = tr.Resolve(); + while (td != null) { + if (td.Interfaces.Any(intf => intf.Name == "IEnumerable" && intf.Namespace == "System.Collections")) + return true; + td = td.BaseType != null ? td.BaseType.Resolve() : null; + } + return false; + } + + /// <summary> + /// Gets whether 'expr' represents a setter in an object initializer. + /// ('CallvirtSetter(Property, v, value)') + /// </summary> + static bool IsSetterInObjectInitializer(ILExpression expr) + { + if (expr == null) + return false; + if (expr.Code == ILCode.CallvirtSetter || expr.Code == ILCode.CallSetter || expr.Code == ILCode.Stfld) { + return expr.Arguments.Count == 2; + } + return false; + } + + /// <summary> + /// Gets whether 'expr' represents the invocation of an 'Add' method in a collection initializer. + /// </summary> + static bool IsAddMethodCall(ILExpression expr) + { + MethodReference addMethod; + List<ILExpression> args; + if (expr.Match(ILCode.Callvirt, out addMethod, out args) || expr.Match(ILCode.Call, out addMethod, out args)) { + if (addMethod.Name == "Add" && addMethod.HasThis) { + return args.Count >= 2; + } + } + return false; + } + + /// <summary> + /// Parses an object initializer. + /// </summary> + /// <param name="body">ILAst block</param> + /// <param name="pos"> + /// Input: position of the instruction assigning to 'v'. + /// Output: first position after the object initializer + /// </param> + /// <param name="v">The variable that holds the object being initialized</param> + /// <param name="newObjExpr">The newobj instruction</param> + /// <returns>InitObject instruction</returns> + ILExpression ParseObjectInitializer(List<ILNode> body, ref int pos, ILVariable v, ILExpression newObjExpr, bool isCollection, bool isValueType) + { + // Take care not to modify any existing ILExpressions in here. + // We just construct new ones around the old ones, any modifications must wait until the whole + // object/collection initializer was analyzed. + ILExpression objectInitializer = new ILExpression(isCollection ? ILCode.InitCollection : ILCode.InitObject, null, newObjExpr); + List<ILExpression> initializerStack = new List<ILExpression>(); + initializerStack.Add(objectInitializer); + while (++pos < body.Count) { + ILExpression nextExpr = body[pos] as ILExpression; + if (IsSetterInObjectInitializer(nextExpr)) { + if (!AdjustInitializerStack(initializerStack, nextExpr.Arguments[0], v, false, isValueType)) { + CleanupInitializerStackAfterFailedAdjustment(initializerStack); + break; + } + initializerStack[initializerStack.Count - 1].Arguments.Add(nextExpr); + } else if (IsAddMethodCall(nextExpr)) { + if (!AdjustInitializerStack(initializerStack, nextExpr.Arguments[0], v, true, isValueType)) { + CleanupInitializerStackAfterFailedAdjustment(initializerStack); + break; + } + initializerStack[initializerStack.Count - 1].Arguments.Add(nextExpr); + } else { + // can't match any more initializers: end of object initializer + break; + } + } + return objectInitializer; + } + + static bool AdjustInitializerStack(List<ILExpression> initializerStack, ILExpression argument, ILVariable v, bool isCollection, bool isValueType) + { + // Argument is of the form 'getter(getter(...(v)))' + // Unpack it into a list of getters: + List<ILExpression> getters = new List<ILExpression>(); + while (argument.Code == ILCode.CallvirtGetter || argument.Code == ILCode.CallGetter || argument.Code == ILCode.Ldfld) { + getters.Add(argument); + if (argument.Arguments.Count != 1) + return false; + argument = argument.Arguments[0]; + } + // Ensure that the final argument is 'v' + if (isValueType) { + ILVariable loadedVar; + if (!(argument.Match(ILCode.Ldloca, out loadedVar) && loadedVar == v)) + return false; + } else { + if (!argument.MatchLdloc(v)) + return false; + } + // Now compare the getters with those that are currently active on the initializer stack: + int i; + for (i = 1; i <= Math.Min(getters.Count, initializerStack.Count - 1); i++) { + ILExpression g1 = initializerStack[i].Arguments[0]; // getter stored in initializer + ILExpression g2 = getters[getters.Count - i]; // matching getter from argument + if (g1.Operand != g2.Operand) { + // operands differ, so we abort the comparison + break; + } + } + // Remove all initializers from the stack that were not matched with one from the argument: + initializerStack.RemoveRange(i, initializerStack.Count - i); + // Now create new initializers for the remaining arguments: + for (; i <= getters.Count; i++) { + ILExpression g = getters[getters.Count - i]; + MemberReference mr = (MemberReference)g.Operand; + TypeReference returnType; + if (mr is FieldReference) + returnType = TypeAnalysis.GetFieldType((FieldReference)mr); + else + returnType = TypeAnalysis.SubstituteTypeArgs(((MethodReference)mr).ReturnType, mr); + + ILExpression nestedInitializer = new ILExpression( + IsCollectionType(returnType) ? ILCode.InitCollection : ILCode.InitObject, + null, g); + // add new initializer to its parent: + ILExpression parentInitializer = initializerStack[initializerStack.Count - 1]; + if (parentInitializer.Code == ILCode.InitCollection) { + // can't add children to collection initializer + if (parentInitializer.Arguments.Count == 1) { + // convert empty collection initializer to object initializer + parentInitializer.Code = ILCode.InitObject; + } else { + return false; + } + } + parentInitializer.Arguments.Add(nestedInitializer); + initializerStack.Add(nestedInitializer); + } + ILExpression lastInitializer = initializerStack[initializerStack.Count - 1]; + if (isCollection) { + return lastInitializer.Code == ILCode.InitCollection; + } else { + if (lastInitializer.Code == ILCode.InitCollection) { + if (lastInitializer.Arguments.Count == 1) { + // convert empty collection initializer to object initializer + lastInitializer.Code = ILCode.InitObject; + return true; + } else { + return false; + } + } else { + return true; + } + } + } + + static void CleanupInitializerStackAfterFailedAdjustment(List<ILExpression> initializerStack) + { + // There might be empty nested initializers left over; so we'll remove those: + while (initializerStack.Count > 1 && initializerStack[initializerStack.Count - 1].Arguments.Count == 1) { + ILExpression parent = initializerStack[initializerStack.Count - 2]; + Debug.Assert(parent.Arguments.Last() == initializerStack[initializerStack.Count - 1]); + parent.Arguments.RemoveAt(parent.Arguments.Count - 1); + initializerStack.RemoveAt(initializerStack.Count - 1); + } + } + + static void ChangeFirstArgumentToInitializedObject(ILExpression initializer) + { + // Go through all elements in the initializer (so skip the newobj-instr. at the start) + for (int i = 1; i < initializer.Arguments.Count; i++) { + ILExpression element = initializer.Arguments[i]; + if (element.Code == ILCode.InitCollection || element.Code == ILCode.InitObject) { + // nested collection/object initializer + ILExpression getCollection = element.Arguments[0]; + getCollection.Arguments[0] = new ILExpression(ILCode.InitializedObject, null); + ChangeFirstArgumentToInitializedObject(element); // handle the collection elements + } else { + element.Arguments[0] = new ILExpression(ILCode.InitializedObject, null); + } + } + } + } +} diff --git a/ICSharpCode.Decompiler/ILAst/LiftedOperators.cs b/ICSharpCode.Decompiler/ILAst/LiftedOperators.cs new file mode 100644 index 00000000..9edeac74 --- /dev/null +++ b/ICSharpCode.Decompiler/ILAst/LiftedOperators.cs @@ -0,0 +1,528 @@ +// 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.Linq; + +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.ILAst +{ + partial class ILAstOptimizer + { + bool SimplifyLiftedOperators(List<ILNode> body, ILExpression expr, int pos) + { + if (!new PatternMatcher(typeSystem).SimplifyLiftedOperators(expr)) return false; + + var inlining = new ILInlining(method); + while (--pos >= 0 && inlining.InlineIfPossible(body, ref pos)) ; + + return true; + } + + sealed class PatternMatcher + { + readonly TypeSystem typeSystem; + public PatternMatcher(TypeSystem typeSystem) + { + this.typeSystem = typeSystem; + } + + public bool SimplifyLiftedOperators(ILExpression expr) + { + if (Simplify(expr)) return true; + + bool modified = false; + foreach (var a in expr.Arguments) + modified |= SimplifyLiftedOperators(a); + return modified; + } + + abstract class Pattern + { + public readonly Pattern[] Arguments; + + protected Pattern(Pattern[] arguments) + { + Arguments = arguments; + } + + public virtual bool Match(PatternMatcher pm, ILExpression e) + { + if (e.Arguments.Count != Arguments.Length || e.Prefixes != null) return false; + for (int i = 0; i < Arguments.Length; i++) + if (!Arguments[i].Match(pm, e.Arguments[i])) return false; + return true; + } + + public virtual ILExpression BuildNew(PatternMatcher pm) + { + throw new NotSupportedException(); + } + + public static Pattern operator &(Pattern a, Pattern b) + { + return new ILPattern(ILCode.LogicAnd, a, b); + } + + public static Pattern operator |(Pattern a, Pattern b) + { + return new ILPattern(ILCode.LogicOr, a, b); + } + + public static Pattern operator !(Pattern a) + { + return new ILPattern(ILCode.LogicNot, a); + } + } + + sealed class ILPattern : Pattern + { + readonly ILCode code; + + public ILPattern(ILCode code, params Pattern[] arguments) + : base(arguments) + { + this.code = code; + } + + public override bool Match(PatternMatcher pm, ILExpression e) + { + return e.Code == code && base.Match(pm, e); + } + + public override ILExpression BuildNew(PatternMatcher pm) + { + var args = new ILExpression[Arguments.Length]; + for (int i = 0; i < args.Length; i++) args[i] = Arguments[i].BuildNew(pm); + TypeReference t = null; + switch (code) { + case ILCode.Ceq: + case ILCode.Cne: + t = pm.typeSystem.Boolean; + break; + case ILCode.NullCoalescing: + t = args[1].InferredType; + break; + } + return new ILExpression(code, null, args) { InferredType = t }; + } + } + + sealed class MethodPattern : Pattern + { + readonly ILCode code; + readonly string method; + + public MethodPattern(ILCode code, string method, params Pattern[] arguments) + : base(arguments) + { + this.code = code; + this.method = method; + } + + public override bool Match(PatternMatcher pm, ILExpression e) + { + if (e.Code != code) return false; + var m = (MethodReference)e.Operand; + return m.Name == method && TypeAnalysis.IsNullableType(m.DeclaringType) && base.Match(pm, e); + } + } + + enum OperatorType + { + Equality, InEquality, Comparison, Other + } + + sealed class OperatorPattern : Pattern + { + OperatorType type; + bool simple; + + public OperatorPattern() : base(null) { } + + public OperatorPattern(OperatorType type, bool simple) + : this() + { + this.type = type; + this.simple = simple; + } + + public override bool Match(PatternMatcher pm, ILExpression e) + { + switch (e.Code) { + case ILCode.Ceq: + if (type != OperatorType.Equality) return false; + break; + case ILCode.Cne: + if (type != OperatorType.InEquality) return false; + break; + case ILCode.Cgt: + case ILCode.Cgt_Un: + case ILCode.Cge: + case ILCode.Cge_Un: + case ILCode.Clt: + case ILCode.Clt_Un: + case ILCode.Cle: + case ILCode.Cle_Un: + if (type != OperatorType.Comparison) return false; + break; + case ILCode.Add: + case ILCode.Add_Ovf: + case ILCode.Add_Ovf_Un: + case ILCode.Sub: + case ILCode.Sub_Ovf: + case ILCode.Sub_Ovf_Un: + case ILCode.Mul: + case ILCode.Mul_Ovf: + case ILCode.Mul_Ovf_Un: + case ILCode.Div: + case ILCode.Div_Un: + case ILCode.Rem: + case ILCode.Rem_Un: + case ILCode.And: + case ILCode.Or: + case ILCode.Xor: + case ILCode.Shl: + case ILCode.Shr: + case ILCode.Shr_Un: + case ILCode.Not: + case ILCode.Neg: + case ILCode.LogicNot: + if (type != OperatorType.Other) return false; + break; + case ILCode.Call: + var m = e.Operand as MethodReference; + if (m == null || m.HasThis || !m.HasParameters || e.Arguments.Count > 2 || !IsCustomOperator(m.Name)) return false; + break; + default: return false; + } + if (pm.Operator != null) throw new InvalidOperationException(); + pm.Operator = e; + + var a0 = e.Arguments[0]; + if (!simple) return VariableAGetValueOrDefault.Match(pm, a0) && VariableBGetValueOrDefault.Match(pm, e.Arguments[1]); + if (e.Arguments.Count == 1) return VariableAGetValueOrDefault.Match(pm, a0); + if (VariableAGetValueOrDefault.Match(pm, a0)) { + pm.SimpleOperand = e.Arguments[1]; + pm.SimpleLeftOperand = false; + return true; + } + if (VariableAGetValueOrDefault.Match(pm, e.Arguments[1])) { + pm.SimpleOperand = a0; + pm.SimpleLeftOperand = true; + return true; + } + return false; + } + + bool IsCustomOperator(string s) + { + switch (type) { + case OperatorType.Equality: return s == "op_Equality"; + case OperatorType.InEquality: return s == "op_Inequality"; + case OperatorType.Comparison: + if (s.Length < 11 || !s.StartsWith("op_", StringComparison.Ordinal)) return false; + switch (s) { + case "op_GreaterThan": + case "op_GreaterThanOrEqual": + case "op_LessThan": + case "op_LessThanOrEqual": return true; + default: return false; + } + default: + if (s.Length < 10 || !s.StartsWith("op_", StringComparison.Ordinal)) return false; + switch (s) { + case "op_Addition": + case "op_Subtraction": + case "op_Multiply": + case "op_Division": + case "op_Modulus": + case "op_BitwiseAnd": + case "op_BitwiseOr": + case "op_ExclusiveOr": + case "op_LeftShift": + case "op_RightShift": + case "op_UnaryNegation": + case "op_UnaryPlus": + case "op_LogicalNot": + case "op_OnesComplement": + case "op_Increment": + case "op_Decrement": return true; + default: return false; + } + } + } + + public override ILExpression BuildNew(PatternMatcher pm) + { + var res = pm.Operator; + res.Arguments.Clear(); + if (pm.SimpleLeftOperand) res.Arguments.Add(pm.SimpleOperand); + res.Arguments.Add(VariableA.BuildNew(pm)); + if (pm.B != null) res.Arguments.Add(VariableB.BuildNew(pm)); + else if (pm.SimpleOperand != null && !pm.SimpleLeftOperand) res.Arguments.Add(pm.SimpleOperand); + return res; + } + } + + sealed class AnyPattern : Pattern + { + public AnyPattern() : base(null) { } + + public override bool Match(PatternMatcher pm, ILExpression e) + { + if (pm.SimpleOperand != null) throw new InvalidOperationException(); + pm.SimpleOperand = e; + return true; + } + + public override ILExpression BuildNew(PatternMatcher pm) + { + return pm.SimpleOperand; + } + } + + sealed class VariablePattern : Pattern + { + readonly ILCode code; + readonly bool b; + + public VariablePattern(ILCode code, bool b) + : base(null) + { + this.code = code; + this.b = b; + } + + public override bool Match(PatternMatcher pm, ILExpression e) + { + if (e.Code != code) return false; + var v = e.Operand as ILVariable; + return v != null && (b ? Capture(ref pm.B, v) : Capture(ref pm.A, v)); + } + + static bool Capture(ref ILVariable pmvar, ILVariable v) + { + if (pmvar != null) return pmvar == v; + pmvar = v; + return true; + } + + static readonly ILExpression[] EmptyArguments = new ILExpression[0]; + public override ILExpression BuildNew(PatternMatcher pm) + { + var v = b ? pm.B : pm.A; + var e = new ILExpression(ILCode.Ldloc, v, EmptyArguments); + if (TypeAnalysis.IsNullableType(v.Type)) e = new ILExpression(ILCode.ValueOf, null, e); + return e; + } + } + + sealed class BooleanPattern : Pattern + { + public static readonly Pattern False = new BooleanPattern(false), True = new BooleanPattern(true); + + readonly object value; + BooleanPattern(bool value) + : base(null) + { + this.value = Convert.ToInt32(value); + } + + public override bool Match(PatternMatcher pm, ILExpression e) + { + return e.Code == ILCode.Ldc_I4 && TypeAnalysis.IsBoolean(e.InferredType) && Equals(e.Operand, value); + } + + public override ILExpression BuildNew(PatternMatcher pm) + { + // boolean constants are wrapped inside a container to disable simplyfication of equality comparisons + return new ILExpression(ILCode.Wrap, null, new ILExpression(ILCode.Ldc_I4, value)); + } + } + + static readonly Pattern VariableRefA = new VariablePattern(ILCode.Ldloca, false), VariableRefB = new VariablePattern(ILCode.Ldloca, true); + static readonly Pattern VariableA = new VariablePattern(ILCode.Ldloc, false), VariableB = new VariablePattern(ILCode.Ldloc, true); + static readonly Pattern VariableAHasValue = new MethodPattern(ILCode.CallGetter, "get_HasValue", VariableRefA); + static readonly Pattern VariableAGetValueOrDefault = new MethodPattern(ILCode.Call, "GetValueOrDefault", VariableRefA); + static readonly Pattern VariableBHasValue = new MethodPattern(ILCode.CallGetter, "get_HasValue", VariableRefB); + static readonly Pattern VariableBGetValueOrDefault = new MethodPattern(ILCode.Call, "GetValueOrDefault", VariableRefB); + static readonly Pattern CeqHasValue = new ILPattern(ILCode.Ceq, VariableAHasValue, VariableBHasValue); + static readonly Pattern CneHasValue = new ILPattern(ILCode.Cne, VariableAHasValue, VariableBHasValue); + static readonly Pattern AndHasValue = new ILPattern(ILCode.And, VariableAHasValue, VariableBHasValue); + static readonly Pattern Any = new AnyPattern(); + static readonly Pattern OperatorVariableAB = new OperatorPattern(); + + static OperatorPattern OperatorNN(OperatorType type) + { + return new OperatorPattern(type, false); + } + + static OperatorPattern OperatorNV(OperatorType type) + { + return new OperatorPattern(type, true); + } + + static Pattern NewObj(Pattern p) + { + return new MethodPattern(ILCode.Newobj, ".ctor", p); + } + + static readonly Pattern[] Comparisons = new Pattern[] { + /* both operands nullable */ + // == (primitive, decimal) + OperatorNN(OperatorType.Equality) & CeqHasValue, + // == (struct) + CeqHasValue & (!VariableAHasValue | OperatorNN(OperatorType.Equality)), + // != (primitive, decimal) + OperatorNN(OperatorType.InEquality) | CneHasValue, + // != (struct) + CneHasValue | (VariableAHasValue & OperatorNN(OperatorType.InEquality)), + // > , < , >= , <= (primitive, decimal) + OperatorNN(OperatorType.Comparison) & AndHasValue, + // > , < , >= , <= (struct) + AndHasValue & OperatorNN(OperatorType.Comparison), + + /* only one operand nullable */ + // == (primitive, decimal) + OperatorNV(OperatorType.Equality) & VariableAHasValue, + // == (struct) + VariableAHasValue & OperatorNV(OperatorType.Equality), + // != (primitive, decimal) + OperatorNV(OperatorType.InEquality) | !VariableAHasValue, + // != (struct) + !VariableAHasValue | OperatorNV(OperatorType.InEquality), + // > , <, >= , <= (primitive, decimal) + OperatorNV(OperatorType.Comparison) & VariableAHasValue, + // > , < , >= , <= (struct) + VariableAHasValue & OperatorNV(OperatorType.Comparison), + }; + + static readonly Pattern[] Other = new Pattern[] { + /* both operands nullable */ + // & (bool) + new ILPattern(ILCode.TernaryOp, VariableAGetValueOrDefault | (!VariableBGetValueOrDefault & !VariableAHasValue), VariableB, VariableA), + new ILPattern(ILCode.And, VariableA, VariableB), + // | (bool) + new ILPattern(ILCode.TernaryOp, VariableAGetValueOrDefault | (!VariableBGetValueOrDefault & !VariableAHasValue), VariableA, VariableB), + new ILPattern(ILCode.Or, VariableA, VariableB), + // null coalescing + new ILPattern(ILCode.TernaryOp, VariableAHasValue, NewObj(VariableAGetValueOrDefault), VariableB), + new ILPattern(ILCode.NullCoalescing, VariableA, VariableB), + // all other + new ILPattern(ILCode.TernaryOp, AndHasValue, NewObj(OperatorNN(OperatorType.Other)), new ILPattern(ILCode.DefaultValue)), + OperatorVariableAB, + + /* only one operand nullable */ + // & (bool) + new ILPattern(ILCode.TernaryOp, Any, VariableA, NewObj(BooleanPattern.False)), + new ILPattern(ILCode.And, VariableA, Any), + // | (bool) + new ILPattern(ILCode.TernaryOp, Any, NewObj(BooleanPattern.True), VariableA), + new ILPattern(ILCode.Or, VariableA, Any), + // == true + VariableAGetValueOrDefault & VariableAHasValue, + new ILPattern(ILCode.Ceq, VariableA, BooleanPattern.True), + // != true + !VariableAGetValueOrDefault | !VariableAHasValue, + new ILPattern(ILCode.Cne, VariableA, BooleanPattern.True), + // == false + !VariableAGetValueOrDefault & VariableAHasValue, + new ILPattern(ILCode.Ceq, VariableA, BooleanPattern.False), + // != false + VariableAGetValueOrDefault | !VariableAHasValue, + new ILPattern(ILCode.Cne, VariableA, BooleanPattern.False), + // ?? true + !VariableAHasValue | VariableAGetValueOrDefault, + new ILPattern(ILCode.NullCoalescing, VariableA, BooleanPattern.True), + // ?? false + VariableAHasValue & VariableAGetValueOrDefault, + new ILPattern(ILCode.NullCoalescing, VariableA, BooleanPattern.False), + // null coalescing + new ILPattern(ILCode.TernaryOp, VariableAHasValue, VariableAGetValueOrDefault, Any), + new ILPattern(ILCode.NullCoalescing, VariableA, Any), + // all other + new ILPattern(ILCode.TernaryOp, VariableAHasValue, NewObj(OperatorNV(OperatorType.Other)), new ILPattern(ILCode.DefaultValue)), + OperatorVariableAB, + }; + + ILVariable A, B; + ILExpression Operator, SimpleOperand; + bool SimpleLeftOperand; + + void Reset() + { + A = null; + B = null; + Operator = null; + SimpleOperand = null; + SimpleLeftOperand = false; + } + + bool Simplify(ILExpression expr) + { + if (expr.Code == ILCode.TernaryOp || expr.Code == ILCode.LogicAnd || expr.Code == ILCode.LogicOr) { + Pattern[] ps; + if (expr.Code != ILCode.TernaryOp) { + ps = Comparisons; + for (int i = 0; i < ps.Length; i++) { + Reset(); + if (!ps[i].Match(this, expr)) continue; + SetResult(expr, OperatorVariableAB.BuildNew(this)); + return true; + } + } + ps = Other; + for (int i = 0; i < ps.Length; i += 2) { + Reset(); + if (!ps[i].Match(this, expr)) continue; + var n = ps[i + 1].BuildNew(this); + SetResult(expr, n); + if (n.Code == ILCode.NullCoalescing) { + // if both operands are nullable then the result is also nullable + if (n.Arguments[1].Code == ILCode.ValueOf) { + n.Arguments[0] = n.Arguments[0].Arguments[0]; + n.Arguments[1] = n.Arguments[1].Arguments[0]; + } + } else if (n.Code != ILCode.Ceq && n.Code != ILCode.Cne) { + expr.Code = ILCode.NullableOf; + expr.InferredType = expr.ExpectedType = null; + } + return true; + } + } + return false; + } + + static void SetResult(ILExpression expr, ILExpression n) + { + // IL ranges from removed nodes are assigned to the new operator expression + var removednodes = expr.GetSelfAndChildrenRecursive<ILExpression>().Except(n.GetSelfAndChildrenRecursive<ILExpression>()); + n.ILRanges.AddRange(removednodes.SelectMany(el => el.ILRanges)); + // the new expression is wrapped in a container so that negations aren't pushed through lifted comparison operations + expr.Code = ILCode.Wrap; + expr.Arguments.Clear(); + expr.Arguments.Add(n); + expr.ILRanges.Clear(); + expr.InferredType = n.InferredType; + } + } + } +} diff --git a/ICSharpCode.Decompiler/ILAst/LoopsAndConditions.cs b/ICSharpCode.Decompiler/ILAst/LoopsAndConditions.cs new file mode 100644 index 00000000..32095b03 --- /dev/null +++ b/ICSharpCode.Decompiler/ILAst/LoopsAndConditions.cs @@ -0,0 +1,443 @@ +// 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.Linq; + +using ICSharpCode.Decompiler.FlowAnalysis; + +namespace ICSharpCode.Decompiler.ILAst +{ + /// <summary> + /// Description of LoopsAndConditions. + /// </summary> + public class LoopsAndConditions + { + Dictionary<ILLabel, ControlFlowNode> labelToCfNode = new Dictionary<ILLabel, ControlFlowNode>(); + + readonly DecompilerContext context; + + uint nextLabelIndex = 0; + + public LoopsAndConditions(DecompilerContext context) + { + this.context = context; + } + + public void FindLoops(ILBlock block) + { + if (block.Body.Count > 0) { + ControlFlowGraph graph; + graph = BuildGraph(block.Body, (ILLabel)block.EntryGoto.Operand); + graph.ComputeDominance(context.CancellationToken); + graph.ComputeDominanceFrontier(); + block.Body = FindLoops(new HashSet<ControlFlowNode>(graph.Nodes.Skip(3)), graph.EntryPoint, false); + } + } + + public void FindConditions(ILBlock block) + { + if (block.Body.Count > 0) { + ControlFlowGraph graph; + graph = BuildGraph(block.Body, (ILLabel)block.EntryGoto.Operand); + graph.ComputeDominance(context.CancellationToken); + graph.ComputeDominanceFrontier(); + block.Body = FindConditions(new HashSet<ControlFlowNode>(graph.Nodes.Skip(3)), graph.EntryPoint); + } + } + + ControlFlowGraph BuildGraph(List<ILNode> nodes, ILLabel entryLabel) + { + int index = 0; + List<ControlFlowNode> cfNodes = new List<ControlFlowNode>(); + ControlFlowNode entryPoint = new ControlFlowNode(index++, 0, ControlFlowNodeType.EntryPoint); + cfNodes.Add(entryPoint); + ControlFlowNode regularExit = new ControlFlowNode(index++, -1, ControlFlowNodeType.RegularExit); + cfNodes.Add(regularExit); + ControlFlowNode exceptionalExit = new ControlFlowNode(index++, -1, ControlFlowNodeType.ExceptionalExit); + cfNodes.Add(exceptionalExit); + + // Create graph nodes + labelToCfNode = new Dictionary<ILLabel, ControlFlowNode>(); + Dictionary<ILNode, ControlFlowNode> astNodeToCfNode = new Dictionary<ILNode, ControlFlowNode>(); + foreach(ILBasicBlock node in nodes) { + ControlFlowNode cfNode = new ControlFlowNode(index++, -1, ControlFlowNodeType.Normal); + cfNodes.Add(cfNode); + astNodeToCfNode[node] = cfNode; + cfNode.UserData = node; + + // Find all contained labels + foreach(ILLabel label in node.GetSelfAndChildrenRecursive<ILLabel>()) { + labelToCfNode[label] = cfNode; + } + } + + // Entry endge + ControlFlowNode entryNode = labelToCfNode[entryLabel]; + ControlFlowEdge entryEdge = new ControlFlowEdge(entryPoint, entryNode, JumpType.Normal); + entryPoint.Outgoing.Add(entryEdge); + entryNode.Incoming.Add(entryEdge); + + // Create edges + foreach(ILBasicBlock node in nodes) { + ControlFlowNode source = astNodeToCfNode[node]; + + // Find all branches + foreach(ILLabel target in node.GetSelfAndChildrenRecursive<ILExpression>(e => e.IsBranch()).SelectMany(e => e.GetBranchTargets())) { + ControlFlowNode destination; + // Labels which are out of out scope will not be in the collection + // Insert self edge only if we are sure we are a loop + if (labelToCfNode.TryGetValue(target, out destination) && (destination != source || target == node.Body.FirstOrDefault())) { + ControlFlowEdge edge = new ControlFlowEdge(source, destination, JumpType.Normal); + source.Outgoing.Add(edge); + destination.Incoming.Add(edge); + } + } + } + + return new ControlFlowGraph(cfNodes.ToArray()); + } + + List<ILNode> FindLoops(HashSet<ControlFlowNode> scope, ControlFlowNode entryPoint, bool excludeEntryPoint) + { + List<ILNode> result = new List<ILNode>(); + + // Do not modify entry data + scope = new HashSet<ControlFlowNode>(scope); + + Queue<ControlFlowNode> agenda = new Queue<ControlFlowNode>(); + agenda.Enqueue(entryPoint); + while(agenda.Count > 0) { + ControlFlowNode node = agenda.Dequeue(); + + // If the node is a loop header + if (scope.Contains(node) + && node.DominanceFrontier.Contains(node) + && (node != entryPoint || !excludeEntryPoint)) + { + HashSet<ControlFlowNode> loopContents = FindLoopContent(scope, node); + + // If the first expression is a loop condition + ILBasicBlock basicBlock = (ILBasicBlock)node.UserData; + ILExpression condExpr; + ILLabel trueLabel; + ILLabel falseLabel; + // It has to be just brtrue - any preceding code would introduce goto + if(basicBlock.MatchSingleAndBr(ILCode.Brtrue, out trueLabel, out condExpr, out falseLabel)) + { + ControlFlowNode trueTarget; + labelToCfNode.TryGetValue(trueLabel, out trueTarget); + ControlFlowNode falseTarget; + labelToCfNode.TryGetValue(falseLabel, out falseTarget); + + // If one point inside the loop and the other outside + if ((!loopContents.Contains(trueTarget) && loopContents.Contains(falseTarget)) || + (loopContents.Contains(trueTarget) && !loopContents.Contains(falseTarget)) ) + { + loopContents.RemoveOrThrow(node); + scope.RemoveOrThrow(node); + + // If false means enter the loop + if (loopContents.Contains(falseTarget) || falseTarget == node) + { + // Negate the condition + condExpr = new ILExpression(ILCode.LogicNot, null, condExpr); + ILLabel tmp = trueLabel; + trueLabel = falseLabel; + falseLabel = tmp; + } + + ControlFlowNode postLoopTarget; + labelToCfNode.TryGetValue(falseLabel, out postLoopTarget); + if (postLoopTarget != null) { + // Pull more nodes into the loop + HashSet<ControlFlowNode> postLoopContents = FindDominatedNodes(scope, postLoopTarget); + var pullIn = scope.Except(postLoopContents).Where(n => node.Dominates(n)); + loopContents.UnionWith(pullIn); + } + + // Use loop to implement the brtrue + basicBlock.Body.RemoveTail(ILCode.Brtrue, ILCode.Br); + basicBlock.Body.Add(new ILWhileLoop() { + Condition = condExpr, + BodyBlock = new ILBlock() { + EntryGoto = new ILExpression(ILCode.Br, trueLabel), + Body = FindLoops(loopContents, node, false) + } + }); + basicBlock.Body.Add(new ILExpression(ILCode.Br, falseLabel)); + result.Add(basicBlock); + + scope.ExceptWith(loopContents); + } + } + + // Fallback method: while(true) + if (scope.Contains(node)) { + result.Add(new ILBasicBlock() { + Body = new List<ILNode>() { + new ILLabel() { Name = "Loop_" + (nextLabelIndex++) }, + new ILWhileLoop() { + BodyBlock = new ILBlock() { + EntryGoto = new ILExpression(ILCode.Br, (ILLabel)basicBlock.Body.First()), + Body = FindLoops(loopContents, node, true) + } + }, + }, + }); + + scope.ExceptWith(loopContents); + } + } + + // Using the dominator tree should ensure we find the the widest loop first + foreach(var child in node.DominatorTreeChildren) { + agenda.Enqueue(child); + } + } + + // Add whatever is left + foreach(var node in scope) { + result.Add((ILNode)node.UserData); + } + scope.Clear(); + + return result; + } + + List<ILNode> FindConditions(HashSet<ControlFlowNode> scope, ControlFlowNode entryNode) + { + List<ILNode> result = new List<ILNode>(); + + // Do not modify entry data + scope = new HashSet<ControlFlowNode>(scope); + + Stack<ControlFlowNode> agenda = new Stack<ControlFlowNode>(); + agenda.Push(entryNode); + while(agenda.Count > 0) { + ControlFlowNode node = agenda.Pop(); + + // Find a block that represents a simple condition + if (scope.Contains(node)) { + + ILBasicBlock block = (ILBasicBlock)node.UserData; + + { + // Switch + ILLabel[] caseLabels; + ILExpression switchArg; + ILLabel fallLabel; + if (block.MatchLastAndBr(ILCode.Switch, out caseLabels, out switchArg, out fallLabel)) { + + // Replace the switch code with ILSwitch + ILSwitch ilSwitch = new ILSwitch() { Condition = switchArg }; + block.Body.RemoveTail(ILCode.Switch, ILCode.Br); + block.Body.Add(ilSwitch); + block.Body.Add(new ILExpression(ILCode.Br, fallLabel)); + result.Add(block); + + // Remove the item so that it is not picked up as content + scope.RemoveOrThrow(node); + + // Find the switch offset + int addValue = 0; + List<ILExpression> subArgs; + if (ilSwitch.Condition.Match(ILCode.Sub, out subArgs) && subArgs[1].Match(ILCode.Ldc_I4, out addValue)) { + ilSwitch.Condition = subArgs[0]; + } + + // Pull in code of cases + ControlFlowNode fallTarget = null; + labelToCfNode.TryGetValue(fallLabel, out fallTarget); + + HashSet<ControlFlowNode> frontiers = new HashSet<ControlFlowNode>(); + if (fallTarget != null) + frontiers.UnionWith(fallTarget.DominanceFrontier.Except(new [] { fallTarget })); + + foreach(ILLabel condLabel in caseLabels) { + ControlFlowNode condTarget = null; + labelToCfNode.TryGetValue(condLabel, out condTarget); + if (condTarget != null) + frontiers.UnionWith(condTarget.DominanceFrontier.Except(new [] { condTarget })); + } + + for (int i = 0; i < caseLabels.Length; i++) { + ILLabel condLabel = caseLabels[i]; + + // Find or create new case block + ILSwitch.CaseBlock caseBlock = ilSwitch.CaseBlocks.FirstOrDefault(b => b.EntryGoto.Operand == condLabel); + if (caseBlock == null) { + caseBlock = new ILSwitch.CaseBlock() { + Values = new List<int>(), + EntryGoto = new ILExpression(ILCode.Br, condLabel) + }; + ilSwitch.CaseBlocks.Add(caseBlock); + + ControlFlowNode condTarget = null; + labelToCfNode.TryGetValue(condLabel, out condTarget); + if (condTarget != null && !frontiers.Contains(condTarget)) { + HashSet<ControlFlowNode> content = FindDominatedNodes(scope, condTarget); + scope.ExceptWith(content); + caseBlock.Body.AddRange(FindConditions(content, condTarget)); + // Add explicit break which should not be used by default, but the goto removal might decide to use it + caseBlock.Body.Add(new ILBasicBlock() { + Body = { + new ILLabel() { Name = "SwitchBreak_" + (nextLabelIndex++) }, + new ILExpression(ILCode.LoopOrSwitchBreak, null) + } + }); + } + } + caseBlock.Values.Add(i + addValue); + } + + // Heuristis to determine if we want to use fallthough as default case + if (fallTarget != null && !frontiers.Contains(fallTarget)) { + HashSet<ControlFlowNode> content = FindDominatedNodes(scope, fallTarget); + if (content.Any()) { + var caseBlock = new ILSwitch.CaseBlock() { EntryGoto = new ILExpression(ILCode.Br, fallLabel) }; + ilSwitch.CaseBlocks.Add(caseBlock); + block.Body.RemoveTail(ILCode.Br); + + scope.ExceptWith(content); + caseBlock.Body.AddRange(FindConditions(content, fallTarget)); + // Add explicit break which should not be used by default, but the goto removal might decide to use it + caseBlock.Body.Add(new ILBasicBlock() { + Body = { + new ILLabel() { Name = "SwitchBreak_" + (nextLabelIndex++) }, + new ILExpression(ILCode.LoopOrSwitchBreak, null) + } + }); + } + } + } + + // Two-way branch + ILExpression condExpr; + ILLabel trueLabel; + ILLabel falseLabel; + if(block.MatchLastAndBr(ILCode.Brtrue, out trueLabel, out condExpr, out falseLabel)) { + + // Swap bodies since that seems to be the usual C# order + ILLabel temp = trueLabel; + trueLabel = falseLabel; + falseLabel = temp; + condExpr = new ILExpression(ILCode.LogicNot, null, condExpr); + + // Convert the brtrue to ILCondition + ILCondition ilCond = new ILCondition() { + Condition = condExpr, + TrueBlock = new ILBlock() { EntryGoto = new ILExpression(ILCode.Br, trueLabel) }, + FalseBlock = new ILBlock() { EntryGoto = new ILExpression(ILCode.Br, falseLabel) } + }; + block.Body.RemoveTail(ILCode.Brtrue, ILCode.Br); + block.Body.Add(ilCond); + result.Add(block); + + // Remove the item immediately so that it is not picked up as content + scope.RemoveOrThrow(node); + + ControlFlowNode trueTarget = null; + labelToCfNode.TryGetValue(trueLabel, out trueTarget); + ControlFlowNode falseTarget = null; + labelToCfNode.TryGetValue(falseLabel, out falseTarget); + + // Pull in the conditional code + if (trueTarget != null && HasSingleEdgeEnteringBlock(trueTarget)) { + HashSet<ControlFlowNode> content = FindDominatedNodes(scope, trueTarget); + scope.ExceptWith(content); + ilCond.TrueBlock.Body.AddRange(FindConditions(content, trueTarget)); + } + if (falseTarget != null && HasSingleEdgeEnteringBlock(falseTarget)) { + HashSet<ControlFlowNode> content = FindDominatedNodes(scope, falseTarget); + scope.ExceptWith(content); + ilCond.FalseBlock.Body.AddRange(FindConditions(content, falseTarget)); + } + } + } + + // Add the node now so that we have good ordering + if (scope.Contains(node)) { + result.Add((ILNode)node.UserData); + scope.Remove(node); + } + } + + // depth-first traversal of dominator tree + for (int i = node.DominatorTreeChildren.Count - 1; i >= 0; i--) { + agenda.Push(node.DominatorTreeChildren[i]); + } + } + + // Add whatever is left + foreach(var node in scope) { + result.Add((ILNode)node.UserData); + } + + return result; + } + + static bool HasSingleEdgeEnteringBlock(ControlFlowNode node) + { + return node.Incoming.Count(edge => !node.Dominates(edge.Source)) == 1; + } + + static HashSet<ControlFlowNode> FindDominatedNodes(HashSet<ControlFlowNode> scope, ControlFlowNode head) + { + HashSet<ControlFlowNode> agenda = new HashSet<ControlFlowNode>(); + HashSet<ControlFlowNode> result = new HashSet<ControlFlowNode>(); + agenda.Add(head); + + while(agenda.Count > 0) { + ControlFlowNode addNode = agenda.First(); + agenda.Remove(addNode); + + if (scope.Contains(addNode) && head.Dominates(addNode) && result.Add(addNode)) { + foreach (var successor in addNode.Successors) { + agenda.Add(successor); + } + } + } + + return result; + } + + static HashSet<ControlFlowNode> FindLoopContent(HashSet<ControlFlowNode> scope, ControlFlowNode head) + { + var viaBackEdges = head.Predecessors.Where(p => head.Dominates(p)); + HashSet<ControlFlowNode> agenda = new HashSet<ControlFlowNode>(viaBackEdges); + HashSet<ControlFlowNode> result = new HashSet<ControlFlowNode>(); + + while(agenda.Count > 0) { + ControlFlowNode addNode = agenda.First(); + agenda.Remove(addNode); + + if (scope.Contains(addNode) && head.Dominates(addNode) && result.Add(addNode)) { + foreach (var predecessor in addNode.Predecessors) { + agenda.Add(predecessor); + } + } + } + if (scope.Contains(head)) + result.Add(head); + + return result; + } + } +} diff --git a/ICSharpCode.Decompiler/ILAst/PatternMatching.cs b/ICSharpCode.Decompiler/ILAst/PatternMatching.cs new file mode 100644 index 00000000..441088b9 --- /dev/null +++ b/ICSharpCode.Decompiler/ILAst/PatternMatching.cs @@ -0,0 +1,177 @@ +// 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 System.Linq; +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.ILAst +{ + public static class PatternMatching + { + public static bool Match(this ILNode node, ILCode code) + { + ILExpression expr = node as ILExpression; + return expr != null && expr.Prefixes == null && expr.Code == code; + } + + public static bool Match<T>(this ILNode node, ILCode code, out T operand) + { + ILExpression expr = node as ILExpression; + if (expr != null && expr.Prefixes == null && expr.Code == code && expr.Arguments.Count == 0) { + operand = (T)expr.Operand; + return true; + } + operand = default(T); + return false; + } + + public static bool Match(this ILNode node, ILCode code, out List<ILExpression> args) + { + ILExpression expr = node as ILExpression; + if (expr != null && expr.Prefixes == null && expr.Code == code) { + Debug.Assert(expr.Operand == null); + args = expr.Arguments; + return true; + } + args = null; + return false; + } + + public static bool Match(this ILNode node, ILCode code, out ILExpression arg) + { + List<ILExpression> args; + if (node.Match(code, out args) && args.Count == 1) { + arg = args[0]; + return true; + } + arg = null; + return false; + } + + public static bool Match<T>(this ILNode node, ILCode code, out T operand, out List<ILExpression> args) + { + ILExpression expr = node as ILExpression; + if (expr != null && expr.Prefixes == null && expr.Code == code) { + operand = (T)expr.Operand; + args = expr.Arguments; + return true; + } + operand = default(T); + args = null; + return false; + } + + public static bool Match<T>(this ILNode node, ILCode code, out T operand, out ILExpression arg) + { + List<ILExpression> args; + if (node.Match(code, out operand, out args) && args.Count == 1) { + arg = args[0]; + return true; + } + arg = null; + return false; + } + + public static bool Match<T>(this ILNode node, ILCode code, out T operand, out ILExpression arg1, out ILExpression arg2) + { + List<ILExpression> args; + if (node.Match(code, out operand, out args) && args.Count == 2) { + arg1 = args[0]; + arg2 = args[1]; + return true; + } + arg1 = null; + arg2 = null; + return false; + } + + public static bool MatchSingle<T>(this ILBasicBlock bb, ILCode code, out T operand, out ILExpression arg) + { + if (bb.Body.Count == 2 && + bb.Body[0] is ILLabel && + bb.Body[1].Match(code, out operand, out arg)) + { + return true; + } + operand = default(T); + arg = null; + return false; + } + + public static bool MatchSingleAndBr<T>(this ILBasicBlock bb, ILCode code, out T operand, out ILExpression arg, out ILLabel brLabel) + { + if (bb.Body.Count == 3 && + bb.Body[0] is ILLabel && + bb.Body[1].Match(code, out operand, out arg) && + bb.Body[2].Match(ILCode.Br, out brLabel)) + { + return true; + } + operand = default(T); + arg = null; + brLabel = null; + return false; + } + + public static bool MatchLastAndBr<T>(this ILBasicBlock bb, ILCode code, out T operand, out ILExpression arg, out ILLabel brLabel) + { + if (bb.Body.ElementAtOrDefault(bb.Body.Count - 2).Match(code, out operand, out arg) && + bb.Body.LastOrDefault().Match(ILCode.Br, out brLabel)) + { + return true; + } + operand = default(T); + arg = null; + brLabel = null; + return false; + } + + public static bool MatchThis(this ILNode node) + { + ILVariable v; + return node.Match(ILCode.Ldloc, out v) && v.IsParameter && v.OriginalParameter.Index == -1; + } + + public static bool MatchLdloc(this ILNode node, ILVariable expectedVar) + { + 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; + } + + public static bool MatchLdcI4(this ILNode node, int expectedValue) + { + int v; + return node.Match(ILCode.Ldc_I4, out v) && v == expectedValue; + } + } +} diff --git a/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs b/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs new file mode 100644 index 00000000..f514aa45 --- /dev/null +++ b/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs @@ -0,0 +1,1103 @@ +// 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 System.Linq; + +using ICSharpCode.NRefactory.Utils; +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.ILAst +{ + public partial class ILAstOptimizer + { + #region TypeConversionSimplifications + static bool TypeConversionSimplifications(List<ILNode> body, ILExpression expr, int pos) + { + bool modified = false; + modified |= TransformDecimalCtorToConstant(expr); + modified |= SimplifyLdcI4ConvI8(expr); + modified |= RemoveConvIFromArrayCreation(expr); + foreach(ILExpression arg in expr.Arguments) { + modified |= TypeConversionSimplifications(null, arg, -1); + } + return modified; + } + + static bool TransformDecimalCtorToConstant(ILExpression expr) + { + MethodReference r; + List<ILExpression> args; + if (expr.Match(ILCode.Newobj, out r, out args) && + r.DeclaringType.Namespace == "System" && + r.DeclaringType.Name == "Decimal") + { + if (args.Count == 1) { + int val; + if (args[0].Match(ILCode.Ldc_I4, out val)) { + expr.Code = ILCode.Ldc_Decimal; + expr.Operand = new decimal(val); + expr.InferredType = r.DeclaringType; + expr.Arguments.Clear(); + return true; + } + } else if (args.Count == 5) { + int lo, mid, hi, isNegative, scale; + if (expr.Arguments[0].Match(ILCode.Ldc_I4, out lo) && + expr.Arguments[1].Match(ILCode.Ldc_I4, out mid) && + expr.Arguments[2].Match(ILCode.Ldc_I4, out hi) && + expr.Arguments[3].Match(ILCode.Ldc_I4, out isNegative) && + expr.Arguments[4].Match(ILCode.Ldc_I4, out scale)) + { + expr.Code = ILCode.Ldc_Decimal; + expr.Operand = new decimal(lo, mid, hi, isNegative != 0, (byte)scale); + expr.InferredType = r.DeclaringType; + expr.Arguments.Clear(); + return true; + } + } + } + return false; + } + + static bool SimplifyLdcI4ConvI8(ILExpression expr) + { + ILExpression ldc; + int val; + if (expr.Match(ILCode.Conv_I8, out ldc) && ldc.Match(ILCode.Ldc_I4, out val)) { + expr.Code = ILCode.Ldc_I8; + expr.Operand = (long)val; + expr.Arguments.Clear(); + return true; + } + return false; + } + + static bool RemoveConvIFromArrayCreation(ILExpression expr) + { + TypeReference typeRef; + ILExpression length; + ILExpression input; + if (expr.Match(ILCode.Newarr, out typeRef, out length)) { + if (length.Match(ILCode.Conv_Ovf_I, out input) || length.Match(ILCode.Conv_I, out input) + || length.Match(ILCode.Conv_Ovf_I_Un, out input) || length.Match(ILCode.Conv_U, out input)) + { + expr.Arguments[0] = input; + return true; + } + } + return false; + } + #endregion + + #region SimplifyLdObjAndStObj + static bool SimplifyLdObjAndStObj(List<ILNode> body, ILExpression expr, int pos) + { + bool modified = false; + expr = SimplifyLdObjAndStObj(expr, ref modified); + if (modified && body != null) + body[pos] = expr; + for (int i = 0; i < expr.Arguments.Count; i++) { + expr.Arguments[i] = SimplifyLdObjAndStObj(expr.Arguments[i], ref modified); + modified |= SimplifyLdObjAndStObj(null, expr.Arguments[i], -1); + } + return modified; + } + + static ILExpression SimplifyLdObjAndStObj(ILExpression expr, ref bool modified) + { + if (expr.Code == ILCode.Initobj) { + expr.Code = ILCode.Stobj; + expr.Arguments.Add(new ILExpression(ILCode.DefaultValue, expr.Operand)); + modified = true; + } else if (expr.Code == ILCode.Cpobj) { + expr.Code = ILCode.Stobj; + expr.Arguments[1] = new ILExpression(ILCode.Ldobj, expr.Operand, expr.Arguments[1]); + modified = true; + } + ILExpression arg, arg2; + TypeReference type; + ILCode? newCode = null; + if (expr.Match(ILCode.Stobj, out type, out arg, out arg2)) { + switch (arg.Code) { + case ILCode.Ldelema: newCode = ILCode.Stelem_Any; break; + case ILCode.Ldloca: newCode = ILCode.Stloc; break; + case ILCode.Ldflda: newCode = ILCode.Stfld; break; + case ILCode.Ldsflda: newCode = ILCode.Stsfld; break; + } + } else if (expr.Match(ILCode.Ldobj, out type, out arg)) { + switch (arg.Code) { + case ILCode.Ldelema: newCode = ILCode.Ldelem_Any; break; + case ILCode.Ldloca: newCode = ILCode.Ldloc; break; + case ILCode.Ldflda: newCode = ILCode.Ldfld; break; + case ILCode.Ldsflda: newCode = ILCode.Ldsfld; break; + } + } + if (newCode != null) { + arg.Code = newCode.Value; + if (expr.Code == ILCode.Stobj) { + arg.InferredType = expr.InferredType; + arg.ExpectedType = expr.ExpectedType; + arg.Arguments.Add(arg2); + } + arg.ILRanges.AddRange(expr.ILRanges); + modified = true; + return arg; + } else { + return expr; + } + } + #endregion + + #region CachedDelegateInitialization + void CachedDelegateInitializationWithField(ILBlock block, ref int i) + { + // if (logicnot(ldsfld(field))) { + // stsfld(field, newobj(Action::.ctor, ldnull(), ldftn(method))) + // } else { + // } + // ...(..., ldsfld(field), ...) + + ILCondition c = block.Body[i] as ILCondition; + if (c == null || c.Condition == null && c.TrueBlock == null || c.FalseBlock == null) + return; + if (!(c.TrueBlock.Body.Count == 1 && c.FalseBlock.Body.Count == 0)) + return; + if (!c.Condition.Match(ILCode.LogicNot)) + return; + ILExpression condition = c.Condition.Arguments.Single() as ILExpression; + if (condition == null || condition.Code != ILCode.Ldsfld) + return; + FieldDefinition field = ((FieldReference)condition.Operand).ResolveWithinSameModule(); // field is defined in current assembly + if (field == null || !field.IsCompilerGeneratedOrIsInCompilerGeneratedClass()) + return; + ILExpression stsfld = c.TrueBlock.Body[0] as ILExpression; + if (!(stsfld != null && stsfld.Code == ILCode.Stsfld && ((FieldReference)stsfld.Operand).ResolveWithinSameModule() == field)) + return; + ILExpression newObj = stsfld.Arguments[0]; + if (!(newObj.Code == ILCode.Newobj && newObj.Arguments.Count == 2)) + return; + if (newObj.Arguments[0].Code != ILCode.Ldnull) + return; + if (newObj.Arguments[1].Code != ILCode.Ldftn) + return; + MethodDefinition anonymousMethod = ((MethodReference)newObj.Arguments[1].Operand).ResolveWithinSameModule(); // method is defined in current assembly + if (!Ast.Transforms.DelegateConstruction.IsAnonymousMethod(context, anonymousMethod)) + return; + + ILNode followingNode = block.Body.ElementAtOrDefault(i + 1); + if (followingNode != null && followingNode.GetSelfAndChildrenRecursive<ILExpression>().Count( + e => e.Code == ILCode.Ldsfld && ((FieldReference)e.Operand).ResolveWithinSameModule() == field) == 1) + { + foreach (ILExpression parent in followingNode.GetSelfAndChildrenRecursive<ILExpression>()) { + for (int j = 0; j < parent.Arguments.Count; j++) { + if (parent.Arguments[j].Code == ILCode.Ldsfld && ((FieldReference)parent.Arguments[j].Operand).ResolveWithinSameModule() == field) { + parent.Arguments[j] = newObj; + block.Body.RemoveAt(i); + i -= new ILInlining(method).InlineInto(block.Body, i, aggressive: false); + return; + } + } + } + } + } + + void CachedDelegateInitializationWithLocal(ILBlock block, ref int i) + { + // if (logicnot(ldloc(v))) { + // stloc(v, newobj(Action::.ctor, ldloc(displayClass), ldftn(method))) + // } else { + // } + // ...(..., ldloc(v), ...) + + ILCondition c = block.Body[i] as ILCondition; + if (c == null || c.Condition == null && c.TrueBlock == null || c.FalseBlock == null) + return; + if (!(c.TrueBlock.Body.Count == 1 && c.FalseBlock.Body.Count == 0)) + return; + if (!c.Condition.Match(ILCode.LogicNot)) + return; + ILExpression condition = c.Condition.Arguments.Single() as ILExpression; + if (condition == null || condition.Code != ILCode.Ldloc) + return; + ILVariable v = (ILVariable)condition.Operand; + ILExpression stloc = c.TrueBlock.Body[0] as ILExpression; + if (!(stloc != null && stloc.Code == ILCode.Stloc && (ILVariable)stloc.Operand == v)) + return; + ILExpression newObj = stloc.Arguments[0]; + if (!(newObj.Code == ILCode.Newobj && newObj.Arguments.Count == 2)) + return; + if (newObj.Arguments[0].Code != ILCode.Ldloc) + return; + if (newObj.Arguments[1].Code != ILCode.Ldftn) + return; + MethodDefinition anonymousMethod = ((MethodReference)newObj.Arguments[1].Operand).ResolveWithinSameModule(); // method is defined in current assembly + if (!Ast.Transforms.DelegateConstruction.IsAnonymousMethod(context, anonymousMethod)) + return; + + ILNode followingNode = block.Body.ElementAtOrDefault(i + 1); + if (followingNode != null && followingNode.GetSelfAndChildrenRecursive<ILExpression>().Count( + e => e.Code == ILCode.Ldloc && (ILVariable)e.Operand == v) == 1) + { + ILInlining inlining = new ILInlining(method); + if (!(inlining.numLdloc.GetOrDefault(v) == 2 && inlining.numStloc.GetOrDefault(v) == 2 && inlining.numLdloca.GetOrDefault(v) == 0)) + return; + + // Find the store instruction that initializes the local to null: + foreach (ILBlock storeBlock in method.GetSelfAndChildrenRecursive<ILBlock>()) { + for (int j = 0; j < storeBlock.Body.Count; j++) { + ILVariable storedVar; + ILExpression storedExpr; + if (storeBlock.Body[j].Match(ILCode.Stloc, out storedVar, out storedExpr) && storedVar == v && storedExpr.Match(ILCode.Ldnull)) { + // Remove the instruction + storeBlock.Body.RemoveAt(j); + if (storeBlock == block && j < i) + i--; + break; + } + } + } + + block.Body[i] = stloc; // remove the 'if (v==null)' + inlining = new ILInlining(method); + inlining.InlineIfPossible(block.Body, ref i); + } + } + #endregion + + #region MakeAssignmentExpression + bool MakeAssignmentExpression(List<ILNode> body, ILExpression expr, int pos) + { + // exprVar = ... + // stloc(v, exprVar) + // -> + // exprVar = stloc(v, ...)) + ILVariable exprVar; + ILExpression initializer; + if (!(expr.Match(ILCode.Stloc, out exprVar, out initializer) && exprVar.IsGenerated)) + return false; + ILExpression nextExpr = body.ElementAtOrDefault(pos + 1) as ILExpression; + ILVariable v; + ILExpression stLocArg; + if (nextExpr.Match(ILCode.Stloc, out v, out stLocArg) && stLocArg.MatchLdloc(exprVar)) { + ILExpression store2 = body.ElementAtOrDefault(pos + 2) as ILExpression; + if (StoreCanBeConvertedToAssignment(store2, exprVar)) { + // expr_44 = ... + // stloc(v1, expr_44) + // anystore(v2, expr_44) + // -> + // stloc(v1, anystore(v2, ...)) + ILInlining inlining = new ILInlining(method); + if (inlining.numLdloc.GetOrDefault(exprVar) == 2 && inlining.numStloc.GetOrDefault(exprVar) == 1) { + body.RemoveAt(pos + 2); // remove store2 + body.RemoveAt(pos); // remove expr = ... + nextExpr.Arguments[0] = store2; + store2.Arguments[store2.Arguments.Count - 1] = initializer; + + inlining.InlineIfPossible(body, ref pos); + + return true; + } + } + + body.RemoveAt(pos + 1); // remove stloc + nextExpr.Arguments[0] = initializer; + ((ILExpression)body[pos]).Arguments[0] = nextExpr; + return true; + } else if ((nextExpr.Code == ILCode.Stsfld || nextExpr.Code == ILCode.CallSetter || nextExpr.Code == ILCode.CallvirtSetter) && nextExpr.Arguments.Count == 1) { + // exprVar = ... + // stsfld(fld, exprVar) + // -> + // exprVar = stsfld(fld, ...)) + if (nextExpr.Arguments[0].MatchLdloc(exprVar)) { + body.RemoveAt(pos + 1); // remove stsfld + nextExpr.Arguments[0] = initializer; + ((ILExpression)body[pos]).Arguments[0] = nextExpr; + return true; + } + } + return false; + } + + bool StoreCanBeConvertedToAssignment(ILExpression store, ILVariable exprVar) + { + if (store == null) + return false; + switch (store.Code) { + case ILCode.Stloc: + case ILCode.Stfld: + case ILCode.Stsfld: + case ILCode.Stobj: + case ILCode.CallSetter: + case ILCode.CallvirtSetter: + break; + default: + if (!store.Code.IsStoreToArray()) + return false; + break; + } + return store.Arguments.Last().Code == ILCode.Ldloc && store.Arguments.Last().Operand == exprVar; + } + #endregion + + #region MakeCompoundAssignments + bool MakeCompoundAssignments(List<ILNode> body, ILExpression expr, int pos) + { + bool modified = false; + modified |= MakeCompoundAssignment(expr); + // Static fields and local variables are not handled here - those are expressions without side effects + // and get handled by ReplaceMethodCallsWithOperators + // (which does a reversible transform to the short operator form, as the introduction of checked/unchecked might have to revert to the long form). + foreach (ILExpression arg in expr.Arguments) { + modified |= MakeCompoundAssignments(null, arg, -1); + } + if (modified && body != null) + new ILInlining(method).InlineInto(body, pos, aggressive: false); + return modified; + } + + bool MakeCompoundAssignment(ILExpression expr) + { + // stelem.any(T, ldloc(array), ldloc(pos), <OP>(ldelem.any(T, ldloc(array), ldloc(pos)), <RIGHT>)) + // or + // stobj(T, ldloc(ptr), <OP>(ldobj(T, ldloc(ptr)), <RIGHT>)) + ILCode expectedLdelemCode; + switch (expr.Code) { + case ILCode.Stelem_Any: + expectedLdelemCode = ILCode.Ldelem_Any; + break; + case ILCode.Stfld: + expectedLdelemCode = ILCode.Ldfld; + break; + case ILCode.Stobj: + expectedLdelemCode = ILCode.Ldobj; + break; + case ILCode.CallSetter: + expectedLdelemCode = ILCode.CallGetter; + break; + case ILCode.CallvirtSetter: + expectedLdelemCode = ILCode.CallvirtGetter; + break; + default: + return false; + } + + // all arguments except the last (so either array+pos, or ptr): + bool hasGeneratedVar = false; + for (int i = 0; i < expr.Arguments.Count - 1; i++) { + ILVariable inputVar; + if (!expr.Arguments[i].Match(ILCode.Ldloc, out inputVar)) + return false; + hasGeneratedVar |= inputVar.IsGenerated; + } + // At least one of the variables must be generated; otherwise we just keep the expanded form. + // We do this because we want compound assignments to be represented in ILAst only when strictly necessary; + // other compound assignments will be introduced by ReplaceMethodCallsWithOperator + // (which uses a reversible transformation, see ReplaceMethodCallsWithOperator.RestoreOriginalAssignOperatorAnnotation) + if (!hasGeneratedVar) + return false; + + ILExpression op = expr.Arguments.Last(); + // in case of compound assignments with a lifted operator the result is inside NullableOf and the operand is inside ValueOf + bool liftedOperator = false; + if (op.Code == ILCode.NullableOf) { + op = op.Arguments[0]; + liftedOperator = true; + } + if (!CanBeRepresentedAsCompoundAssignment(op)) + return false; + + ILExpression ldelem = op.Arguments[0]; + if (liftedOperator) { + if (ldelem.Code != ILCode.ValueOf) + return false; + ldelem = ldelem.Arguments[0]; + } + if (ldelem.Code != expectedLdelemCode) + return false; + Debug.Assert(ldelem.Arguments.Count == expr.Arguments.Count - 1); + for (int i = 0; i < ldelem.Arguments.Count; i++) { + if (!ldelem.Arguments[i].MatchLdloc((ILVariable)expr.Arguments[i].Operand)) + return false; + } + expr.Code = ILCode.CompoundAssignment; + expr.Operand = null; + expr.Arguments.RemoveRange(0, ldelem.Arguments.Count); + // result is "CompoundAssignment(<OP>(ldelem.any(...), <RIGHT>))" + return true; + } + + static bool CanBeRepresentedAsCompoundAssignment(ILExpression expr) + { + switch (expr.Code) { + case ILCode.Add: + case ILCode.Add_Ovf: + case ILCode.Add_Ovf_Un: + case ILCode.Sub: + case ILCode.Sub_Ovf: + case ILCode.Sub_Ovf_Un: + case ILCode.Mul: + case ILCode.Mul_Ovf: + case ILCode.Mul_Ovf_Un: + case ILCode.Div: + case ILCode.Div_Un: + case ILCode.Rem: + case ILCode.Rem_Un: + case ILCode.And: + case ILCode.Or: + case ILCode.Xor: + case ILCode.Shl: + case ILCode.Shr: + case ILCode.Shr_Un: + return true; + case ILCode.Call: + var m = expr.Operand as MethodReference; + if (m == null || m.HasThis || expr.Arguments.Count != 2) return false; + switch (m.Name) { + case "op_Addition": + case "op_Subtraction": + case "op_Multiply": + case "op_Division": + case "op_Modulus": + case "op_BitwiseAnd": + case "op_BitwiseOr": + case "op_ExclusiveOr": + case "op_LeftShift": + case "op_RightShift": + return true; + default: + return false; + } + default: + return false; + } + } + #endregion + + #region IntroducePostIncrement + + bool IntroducePostIncrement(List<ILNode> body, ILExpression expr, int pos) + { + bool modified = IntroducePostIncrementForVariables(body, expr, pos); + Debug.Assert(body[pos] == expr); // IntroducePostIncrementForVariables shouldn't change the expression reference + ILExpression newExpr = IntroducePostIncrementForInstanceFields(expr); + if (newExpr != null) { + modified = true; + body[pos] = newExpr; + new ILInlining(method).InlineIfPossible(body, ref pos); + } + return modified; + } + + bool IntroducePostIncrementForVariables(List<ILNode> body, ILExpression expr, int pos) + { + // Works for variables and static fields/properties + + // expr = ldloc(i) + // stloc(i, add(expr, ldc.i4(1))) + // -> + // expr = postincrement(1, ldloca(i)) + ILVariable exprVar; + ILExpression exprInit; + if (!(expr.Match(ILCode.Stloc, out exprVar, out exprInit) && exprVar.IsGenerated)) + return false; + + //The next expression + ILExpression nextExpr = body.ElementAtOrDefault(pos + 1) as ILExpression; + if (nextExpr == null) + return false; + + ILCode loadInstruction = exprInit.Code; + ILCode storeInstruction = nextExpr.Code; + bool recombineVariable = false; + + // We only recognise local variables, static fields, and static getters with no arguments + switch (loadInstruction) { + case ILCode.Ldloc: + //Must be a matching store type + if (storeInstruction != ILCode.Stloc) + return false; + ILVariable loadVar = (ILVariable)exprInit.Operand; + ILVariable storeVar = (ILVariable)nextExpr.Operand; + if (loadVar != storeVar) { + if (loadVar.OriginalVariable != null && loadVar.OriginalVariable == storeVar.OriginalVariable) + recombineVariable = true; + else + return false; + } + break; + case ILCode.Ldsfld: + if (storeInstruction != ILCode.Stsfld) + return false; + if (exprInit.Operand != nextExpr.Operand) + return false; + break; + case ILCode.CallGetter: + // non-static getters would have the 'this' argument + if (exprInit.Arguments.Count != 0) + return false; + if (storeInstruction != ILCode.CallSetter) + return false; + if (!IsGetterSetterPair(exprInit.Operand, nextExpr.Operand)) + return false; + break; + default: + return false; + } + + ILExpression addExpr = nextExpr.Arguments[0]; + + int incrementAmount; + ILCode incrementCode = GetIncrementCode(addExpr, out incrementAmount); + if (!(incrementAmount != 0 && addExpr.Arguments[0].MatchLdloc(exprVar))) + return false; + + if (recombineVariable) { + // Split local variable, unsplit these two instances + // replace nextExpr.Operand with exprInit.Operand + ReplaceVariables(method, oldVar => oldVar == nextExpr.Operand ? (ILVariable)exprInit.Operand : oldVar); + } + + switch (loadInstruction) { + case ILCode.Ldloc: + exprInit.Code = ILCode.Ldloca; + break; + case ILCode.Ldsfld: + exprInit.Code = ILCode.Ldsflda; + break; + case ILCode.CallGetter: + exprInit = new ILExpression(ILCode.AddressOf, null, exprInit); + break; + } + expr.Arguments[0] = new ILExpression(incrementCode, incrementAmount, exprInit); + body.RemoveAt(pos + 1); // TODO ILRanges + return true; + } + + static bool IsGetterSetterPair(object getterOperand, object setterOperand) + { + MethodReference getter = getterOperand as MethodReference; + MethodReference setter = setterOperand as MethodReference; + if (getter == null || setter == null) + return false; + if (!TypeAnalysis.IsSameType(getter.DeclaringType, setter.DeclaringType)) + return false; + MethodDefinition getterDef = getter.Resolve(); + MethodDefinition setterDef = setter.Resolve(); + if (getterDef == null || setterDef == null) + return false; + foreach (PropertyDefinition prop in getterDef.DeclaringType.Properties) { + if (prop.GetMethod == getterDef) + return prop.SetMethod == setterDef; + } + return false; + } + + ILExpression IntroducePostIncrementForInstanceFields(ILExpression expr) + { + // stfld(field, ldloc(instance), add(stloc(helperVar, ldfld(field, ldloc(instance))), ldc.i4(1))) + // -> stloc(helperVar, postincrement(1, ldflda(field, ldloc(instance)))) + + // Also works for array elements and pointers: + + // stelem.any(T, ldloc(instance), ldloc(pos), add(stloc(helperVar, ldelem.any(T, ldloc(instance), ldloc(pos))), ldc.i4(1))) + // -> stloc(helperVar, postincrement(1, ldelema(ldloc(instance), ldloc(pos)))) + + // stobj(T, ldloc(ptr), add(stloc(helperVar, ldobj(T, ldloc(ptr)), ldc.i4(1)))) + // -> stloc(helperVar, postIncrement(1, ldloc(ptr))) + + // callsetter(set_P, ldloc(instance), add(stloc(helperVar, callgetter(get_P, ldloc(instance))), ldc.i4(1))) + // -> stloc(helperVar, postIncrement(1, propertyaddress. callgetter(get_P, ldloc(instance)))) + + if (!(expr.Code == ILCode.Stfld || expr.Code.IsStoreToArray() || expr.Code == ILCode.Stobj || expr.Code == ILCode.CallSetter || expr.Code == ILCode.CallvirtSetter)) + return null; + + // Test that all arguments except the last are ldloc (1 arg for fields and pointers, 2 args for arrays) + for (int i = 0; i < expr.Arguments.Count - 1; i++) { + if (expr.Arguments[i].Code != ILCode.Ldloc) + return null; + } + + ILExpression addExpr = expr.Arguments[expr.Arguments.Count - 1]; + int incrementAmount; + ILCode incrementCode = GetIncrementCode(addExpr, out incrementAmount); + ILVariable helperVar; + ILExpression initialValue; + if (!(incrementAmount != 0 && addExpr.Arguments[0].Match(ILCode.Stloc, out helperVar, out initialValue))) + return null; + + if (expr.Code == ILCode.Stfld) { + if (initialValue.Code != ILCode.Ldfld) + return null; + // There might be two different FieldReference instances, so we compare the field's signatures: + FieldReference getField = (FieldReference)initialValue.Operand; + FieldReference setField = (FieldReference)expr.Operand; + if (!(TypeAnalysis.IsSameType(getField.DeclaringType, setField.DeclaringType) + && getField.Name == setField.Name && TypeAnalysis.IsSameType(getField.FieldType, setField.FieldType))) + { + return null; + } + } else if (expr.Code == ILCode.Stobj) { + if (!(initialValue.Code == ILCode.Ldobj && initialValue.Operand == expr.Operand)) + return null; + } else if (expr.Code == ILCode.CallSetter) { + if (!(initialValue.Code == ILCode.CallGetter && IsGetterSetterPair(initialValue.Operand, expr.Operand))) + return null; + } else if (expr.Code == ILCode.CallvirtSetter) { + if (!(initialValue.Code == ILCode.CallvirtGetter && IsGetterSetterPair(initialValue.Operand, expr.Operand))) + return null; + } else { + if (!initialValue.Code.IsLoadFromArray()) + return null; + } + Debug.Assert(expr.Arguments.Count - 1 == initialValue.Arguments.Count); + for (int i = 0; i < initialValue.Arguments.Count; i++) { + if (!initialValue.Arguments[i].MatchLdloc((ILVariable)expr.Arguments[i].Operand)) + return null; + } + + ILExpression stloc = addExpr.Arguments[0]; + if (expr.Code == ILCode.Stobj) { + stloc.Arguments[0] = new ILExpression(ILCode.PostIncrement, incrementAmount, initialValue.Arguments[0]); + } else if (expr.Code == ILCode.CallSetter || expr.Code == ILCode.CallvirtSetter) { + initialValue = new ILExpression(ILCode.AddressOf, null, initialValue); + stloc.Arguments[0] = new ILExpression(ILCode.PostIncrement, incrementAmount, initialValue); + } else { + stloc.Arguments[0] = new ILExpression(ILCode.PostIncrement, incrementAmount, initialValue); + initialValue.Code = (expr.Code == ILCode.Stfld ? ILCode.Ldflda : ILCode.Ldelema); + } + // TODO: ILRanges? + + return stloc; + } + + ILCode GetIncrementCode(ILExpression addExpr, out int incrementAmount) + { + ILCode incrementCode; + bool decrement = false; + switch (addExpr.Code) { + case ILCode.Add: + incrementCode = ILCode.PostIncrement; + break; + case ILCode.Add_Ovf: + incrementCode = ILCode.PostIncrement_Ovf; + break; + case ILCode.Add_Ovf_Un: + incrementCode = ILCode.PostIncrement_Ovf_Un; + break; + case ILCode.Sub: + incrementCode = ILCode.PostIncrement; + decrement = true; + break; + case ILCode.Sub_Ovf: + incrementCode = ILCode.PostIncrement_Ovf; + decrement = true; + break; + case ILCode.Sub_Ovf_Un: + incrementCode = ILCode.PostIncrement_Ovf_Un; + decrement = true; + break; + default: + incrementAmount = 0; + return ILCode.Nop; + } + if (addExpr.Arguments[1].Match(ILCode.Ldc_I4, out incrementAmount)) { + if (incrementAmount == -1 || incrementAmount == 1) { // TODO pointer increment? + if (decrement) + incrementAmount = -incrementAmount; + return incrementCode; + } + } + incrementAmount = 0; + return ILCode.Nop; + } + #endregion + + #region IntroduceFixedStatements + bool IntroduceFixedStatements(List<ILNode> body, int i) + { + ILExpression initValue; + ILVariable pinnedVar; + int initEndPos; + if (!MatchFixedInitializer(body, i, out pinnedVar, out initValue, out initEndPos)) + return false; + + ILFixedStatement fixedStmt = body.ElementAtOrDefault(initEndPos) as ILFixedStatement; + if (fixedStmt != null) { + ILExpression expr = fixedStmt.BodyBlock.Body.LastOrDefault() as ILExpression; + if (expr != null && expr.Code == ILCode.Stloc && expr.Operand == pinnedVar && IsNullOrZero(expr.Arguments[0])) { + // we found a second initializer for the existing fixed statement + fixedStmt.Initializers.Insert(0, initValue); + body.RemoveRange(i, initEndPos - i); + fixedStmt.BodyBlock.Body.RemoveAt(fixedStmt.BodyBlock.Body.Count - 1); + if (pinnedVar.Type.IsByReference) + pinnedVar.Type = new PointerType(((ByReferenceType)pinnedVar.Type).ElementType); + return true; + } + } + + // find where pinnedVar is reset to 0: + int j; + for (j = initEndPos; j < body.Count; j++) { + ILVariable v2; + ILExpression storedVal; + // stloc(pinned_Var, conv.u(ldc.i4(0))) + if (body[j].Match(ILCode.Stloc, out v2, out storedVal) && v2 == pinnedVar) { + if (IsNullOrZero(storedVal)) { + break; + } + } + } + // Create fixed statement from i to j + fixedStmt = new ILFixedStatement(); + fixedStmt.Initializers.Add(initValue); + fixedStmt.BodyBlock = new ILBlock(body.GetRange(initEndPos, j - initEndPos)); // from initEndPos to j-1 (inclusive) + body.RemoveRange(i + 1, Math.Min(j, body.Count - 1) - i); // from i+1 to j (inclusive) + body[i] = fixedStmt; + if (pinnedVar.Type.IsByReference) + pinnedVar.Type = new PointerType(((ByReferenceType)pinnedVar.Type).ElementType); + + return true; + } + + bool IsNullOrZero(ILExpression expr) + { + if (expr.Code == ILCode.Conv_U || expr.Code == ILCode.Conv_I) + expr = expr.Arguments[0]; + return (expr.Code == ILCode.Ldc_I4 && (int)expr.Operand == 0) || expr.Code == ILCode.Ldnull; + } + + bool MatchFixedInitializer(List<ILNode> body, int i, out ILVariable pinnedVar, out ILExpression initValue, out int nextPos) + { + if (body[i].Match(ILCode.Stloc, out pinnedVar, out initValue) && pinnedVar.IsPinned && !IsNullOrZero(initValue)) { + initValue = (ILExpression)body[i]; + nextPos = i + 1; + HandleStringFixing(pinnedVar, body, ref nextPos, ref initValue); + return true; + } + ILCondition ifStmt = body[i] as ILCondition; + ILExpression arrayLoadingExpr; + if (ifStmt != null && MatchFixedArrayInitializerCondition(ifStmt.Condition, out arrayLoadingExpr)) { + ILVariable arrayVariable = (ILVariable)arrayLoadingExpr.Operand; + ILExpression trueValue; + if (ifStmt.TrueBlock != null && ifStmt.TrueBlock.Body.Count == 1 + && ifStmt.TrueBlock.Body[0].Match(ILCode.Stloc, out pinnedVar, out trueValue) + && pinnedVar.IsPinned && IsNullOrZero(trueValue)) + { + if (ifStmt.FalseBlock != null && ifStmt.FalseBlock.Body.Count == 1 && ifStmt.FalseBlock.Body[0] is ILFixedStatement) { + ILFixedStatement fixedStmt = (ILFixedStatement)ifStmt.FalseBlock.Body[0]; + ILVariable stlocVar; + ILExpression falseValue; + if (fixedStmt.Initializers.Count == 1 && fixedStmt.BodyBlock.Body.Count == 0 + && fixedStmt.Initializers[0].Match(ILCode.Stloc, out stlocVar, out falseValue) && stlocVar == pinnedVar) + { + ILVariable loadedVariable; + if (falseValue.Code == ILCode.Ldelema + && falseValue.Arguments[0].Match(ILCode.Ldloc, out loadedVariable) && loadedVariable == arrayVariable + && IsNullOrZero(falseValue.Arguments[1])) + { + // OK, we detected the pattern for fixing an array. + // Now check whether the loading expression was a store ot a temp. var + // that can be eliminated. + if (arrayLoadingExpr.Code == ILCode.Stloc) { + ILInlining inlining = new ILInlining(method); + if (inlining.numLdloc.GetOrDefault(arrayVariable) == 2 && + inlining.numStloc.GetOrDefault(arrayVariable) == 1 && inlining.numLdloca.GetOrDefault(arrayVariable) == 0) + { + arrayLoadingExpr = arrayLoadingExpr.Arguments[0]; + } + } + initValue = new ILExpression(ILCode.Stloc, pinnedVar, arrayLoadingExpr); + nextPos = i + 1; + return true; + } + } + } + } + } + initValue = null; + nextPos = -1; + return false; + } + + bool MatchFixedArrayInitializerCondition(ILExpression condition, out ILExpression initValue) + { + ILExpression logicAnd; + ILVariable arrayVar; + if (condition.Match(ILCode.LogicNot, out logicAnd) && logicAnd.Code == ILCode.LogicAnd) { + initValue = UnpackDoubleNegation(logicAnd.Arguments[0]); + ILExpression arrayVarInitializer; + if (initValue.Match(ILCode.Ldloc, out arrayVar) + || initValue.Match(ILCode.Stloc, out arrayVar, out arrayVarInitializer)) + { + ILExpression arrayLength = logicAnd.Arguments[1]; + if (arrayLength.Code == ILCode.Conv_I4) + arrayLength = arrayLength.Arguments[0]; + return arrayLength.Code == ILCode.Ldlen && arrayLength.Arguments[0].MatchLdloc(arrayVar); + } + } + initValue = null; + return false; + } + + ILExpression UnpackDoubleNegation(ILExpression expr) + { + ILExpression negated; + if (expr.Match(ILCode.LogicNot, out negated) && negated.Match(ILCode.LogicNot, out negated)) + return negated; + else + return expr; + } + + bool HandleStringFixing(ILVariable pinnedVar, List<ILNode> body, ref int pos, ref ILExpression fixedStmtInitializer) + { + // fixed (stloc(pinnedVar, ldloc(text))) { + // var1 = var2 = conv.i(ldloc(pinnedVar)) + // if (logicnot(logicnot(var1))) { + // var2 = add(var1, call(RuntimeHelpers::get_OffsetToStringData)) + // } + // stloc(ptrVar, var2) + // ... + + if (pos >= body.Count) + return false; + + ILVariable var1, var2; + ILExpression varAssignment, ptrInitialization; + if (!(body[pos].Match(ILCode.Stloc, out var1, out varAssignment) && varAssignment.Match(ILCode.Stloc, out var2, out ptrInitialization))) + return false; + if (!(var1.IsGenerated && var2.IsGenerated)) + return false; + if (ptrInitialization.Code == ILCode.Conv_I || ptrInitialization.Code == ILCode.Conv_U) + ptrInitialization = ptrInitialization.Arguments[0]; + if (!ptrInitialization.MatchLdloc(pinnedVar)) + return false; + + ILCondition ifStmt = body[pos + 1] as ILCondition; + if (!(ifStmt != null && ifStmt.TrueBlock != null && ifStmt.TrueBlock.Body.Count == 1 && (ifStmt.FalseBlock == null || ifStmt.FalseBlock.Body.Count == 0))) + return false; + if (!UnpackDoubleNegation(ifStmt.Condition).MatchLdloc(var1)) + return false; + ILVariable assignedVar; + ILExpression assignedExpr; + if (!(ifStmt.TrueBlock.Body[0].Match(ILCode.Stloc, out assignedVar, out assignedExpr) && assignedVar == var2 && assignedExpr.Code == ILCode.Add)) + return false; + MethodReference calledMethod; + if (!(assignedExpr.Arguments[0].MatchLdloc(var1))) + return false; + if (!(assignedExpr.Arguments[1].Match(ILCode.Call, out calledMethod) || assignedExpr.Arguments[1].Match(ILCode.CallGetter, out calledMethod))) + return false; + if (!(calledMethod.Name == "get_OffsetToStringData" && calledMethod.DeclaringType.FullName == "System.Runtime.CompilerServices.RuntimeHelpers")) + return false; + + ILVariable pointerVar; + if (body[pos + 2].Match(ILCode.Stloc, out pointerVar, out assignedExpr) && assignedExpr.MatchLdloc(var2)) { + pos += 3; + fixedStmtInitializer.Operand = pointerVar; + return true; + } + return false; + } + #endregion + + #region SimplifyLogicNot + static bool SimplifyLogicNot(List<ILNode> body, ILExpression expr, int pos) + { + bool modified = false; + expr = SimplifyLogicNot(expr, ref modified); + Debug.Assert(expr == null); + return modified; + } + + static ILExpression SimplifyLogicNot(ILExpression expr, ref bool modified) + { + ILExpression a; + // "ceq(a, ldc.i4.0)" becomes "logicnot(a)" if the inferred type for expression "a" is boolean + if (expr.Code == ILCode.Ceq && TypeAnalysis.IsBoolean(expr.Arguments[0].InferredType) && (a = expr.Arguments[1]).Code == ILCode.Ldc_I4 && (int)a.Operand == 0) { + expr.Code = ILCode.LogicNot; + expr.ILRanges.AddRange(a.ILRanges); + expr.Arguments.RemoveAt(1); + modified = true; + } + + ILExpression res = null; + while (expr.Code == ILCode.LogicNot) { + a = expr.Arguments[0]; + // remove double negation + if (a.Code == ILCode.LogicNot) { + res = a.Arguments[0]; + res.ILRanges.AddRange(expr.ILRanges); + res.ILRanges.AddRange(a.ILRanges); + expr = res; + } else { + if (SimplifyLogicNotArgument(expr)) res = expr = a; + break; + } + } + + for (int i = 0; i < expr.Arguments.Count; i++) { + a = SimplifyLogicNot(expr.Arguments[i], ref modified); + if (a != null) { + expr.Arguments[i] = a; + modified = true; + } + } + + return res; + } + + /// <summary> + /// If the argument is a binary comparison operation then the negation is pushed through it + /// </summary> + static bool SimplifyLogicNotArgument(ILExpression expr) + { + var a = expr.Arguments[0]; + ILCode c; + switch (a.Code) { + case ILCode.Ceq: c = ILCode.Cne; break; + case ILCode.Cne: c = ILCode.Ceq; break; + case ILCode.Cgt: c = ILCode.Cle; break; + case ILCode.Cgt_Un: c = ILCode.Cle_Un; break; + case ILCode.Cge: c = ILCode.Clt; break; + case ILCode.Cge_Un: c = ILCode.Clt_Un; break; + case ILCode.Clt: c = ILCode.Cge; break; + case ILCode.Clt_Un: c = ILCode.Cge_Un; break; + case ILCode.Cle: c = ILCode.Cgt; break; + case ILCode.Cle_Un: c = ILCode.Cgt_Un; break; + default: return false; + } + a.Code = c; + a.ILRanges.AddRange(expr.ILRanges); + return true; + } + #endregion + + #region SimplifyShiftOperators + static bool SimplifyShiftOperators(List<ILNode> body, ILExpression expr, int pos) + { + // C# compiles "a << b" to "a << (b & 31)", so we will remove the "& 31" if possible. + bool modified = false; + SimplifyShiftOperators(expr, ref modified); + return modified; + } + + static void SimplifyShiftOperators(ILExpression expr, ref bool modified) + { + for (int i = 0; i < expr.Arguments.Count; i++) + SimplifyShiftOperators(expr.Arguments[i], ref modified); + if (expr.Code != ILCode.Shl && expr.Code != ILCode.Shr && expr.Code != ILCode.Shr_Un) + return; + var a = expr.Arguments[1]; + if (a.Code != ILCode.And || a.Arguments[1].Code != ILCode.Ldc_I4 || expr.InferredType == null) + return; + int mask; + switch (expr.InferredType.MetadataType) { + case MetadataType.Int32: + case MetadataType.UInt32: mask = 31; break; + case MetadataType.Int64: + case MetadataType.UInt64: mask = 63; break; + default: return; + } + if ((int)a.Arguments[1].Operand != mask) return; + var res = a.Arguments[0]; + res.ILRanges.AddRange(a.ILRanges); + res.ILRanges.AddRange(a.Arguments[1].ILRanges); + expr.Arguments[1] = res; + modified = true; + } + #endregion + + #region InlineExpressionTreeParameterDeclarations + bool InlineExpressionTreeParameterDeclarations(List<ILNode> body, ILExpression expr, int pos) + { + // When there is a Expression.Lambda() call, and the parameters are declared in the + // IL statement immediately prior to the one containing the Lambda() call, + // using this code for the3 declaration: + // stloc(v, call(Expression::Parameter, call(Type::GetTypeFromHandle, ldtoken(...)), ldstr(...))) + // and the variables v are assigned only once (in that statements), and read only in a Expression::Lambda + // call that immediately follows the assignment statements, then we will inline those assignments + // into the Lambda call using ILCode.ExpressionTreeParameterDeclarations. + + // This is sufficient to allow inlining over the expression tree construction. The remaining translation + // of expression trees into C# will be performed by a C# AST transformer. + + for (int i = expr.Arguments.Count - 1; i >= 0; i--) { + if (InlineExpressionTreeParameterDeclarations(body, expr.Arguments[i], pos)) + return true; + } + + MethodReference mr; + ILExpression lambdaBodyExpr, parameterArray; + if (!(expr.Match(ILCode.Call, out mr, out lambdaBodyExpr, out parameterArray) && mr.Name == "Lambda")) + return false; + if (!(parameterArray.Code == ILCode.InitArray && mr.DeclaringType.FullName == "System.Linq.Expressions.Expression")) + return false; + int firstParameterPos = pos - parameterArray.Arguments.Count; + if (firstParameterPos < 0) + return false; + + ILExpression[] parameterInitExpressions = new ILExpression[parameterArray.Arguments.Count + 1]; + for (int i = 0; i < parameterArray.Arguments.Count; i++) { + parameterInitExpressions[i] = body[firstParameterPos + i] as ILExpression; + if (!MatchParameterVariableAssignment(parameterInitExpressions[i])) + return false; + ILVariable v = (ILVariable)parameterInitExpressions[i].Operand; + if (!parameterArray.Arguments[i].MatchLdloc(v)) + return false; + // TODO: validate that the variable is only used here and within 'body' + } + + parameterInitExpressions[parameterInitExpressions.Length - 1] = lambdaBodyExpr; + Debug.Assert(expr.Arguments[0] == lambdaBodyExpr); + expr.Arguments[0] = new ILExpression(ILCode.ExpressionTreeParameterDeclarations, null, parameterInitExpressions); + + body.RemoveRange(firstParameterPos, parameterArray.Arguments.Count); + + return true; + } + + bool MatchParameterVariableAssignment(ILExpression expr) + { + // stloc(v, call(Expression::Parameter, call(Type::GetTypeFromHandle, ldtoken(...)), ldstr(...))) + ILVariable v; + ILExpression init; + if (!expr.Match(ILCode.Stloc, out v, out init)) + return false; + if (v.IsGenerated || v.IsParameter || v.IsPinned) + return false; + if (v.Type == null || v.Type.FullName != "System.Linq.Expressions.ParameterExpression") + return false; + MethodReference parameterMethod; + ILExpression typeArg, nameArg; + if (!init.Match(ILCode.Call, out parameterMethod, out typeArg, out nameArg)) + return false; + if (!(parameterMethod.Name == "Parameter" && parameterMethod.DeclaringType.FullName == "System.Linq.Expressions.Expression")) + return false; + MethodReference getTypeFromHandle; + ILExpression typeToken; + if (!typeArg.Match(ILCode.Call, out getTypeFromHandle, out typeToken)) + return false; + if (!(getTypeFromHandle.Name == "GetTypeFromHandle" && getTypeFromHandle.DeclaringType.FullName == "System.Type")) + return false; + return typeToken.Code == ILCode.Ldtoken && nameArg.Code == ILCode.Ldstr; + } + #endregion + } +} diff --git a/ICSharpCode.Decompiler/ILAst/SimpleControlFlow.cs b/ICSharpCode.Decompiler/ILAst/SimpleControlFlow.cs new file mode 100644 index 00000000..d3a74a37 --- /dev/null +++ b/ICSharpCode.Decompiler/ILAst/SimpleControlFlow.cs @@ -0,0 +1,376 @@ +// 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 System.Linq; + +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.ILAst +{ + public class SimpleControlFlow + { + Dictionary<ILLabel, int> labelGlobalRefCount = new Dictionary<ILLabel, int>(); + Dictionary<ILLabel, ILBasicBlock> labelToBasicBlock = new Dictionary<ILLabel, ILBasicBlock>(); + + DecompilerContext context; + TypeSystem typeSystem; + + public SimpleControlFlow(DecompilerContext context, ILBlock method) + { + this.context = context; + typeSystem = context.CurrentMethod.Module.TypeSystem; + + foreach(ILLabel target in method.GetSelfAndChildrenRecursive<ILExpression>(e => e.IsBranch()).SelectMany(e => e.GetBranchTargets())) { + labelGlobalRefCount[target] = labelGlobalRefCount.GetOrDefault(target) + 1; + } + foreach(ILBasicBlock bb in method.GetSelfAndChildrenRecursive<ILBasicBlock>()) { + foreach(ILLabel label in bb.GetChildren().OfType<ILLabel>()) { + labelToBasicBlock[label] = bb; + } + } + } + + public bool SimplifyTernaryOperator(List<ILNode> body, ILBasicBlock head, int pos) + { + Debug.Assert(body.Contains(head)); + + ILExpression condExpr; + ILLabel trueLabel; + ILLabel falseLabel; + ILVariable trueLocVar = null; + ILExpression trueExpr; + ILLabel trueFall; + ILVariable falseLocVar = null; + ILExpression falseExpr; + ILLabel falseFall; + object unused; + + if (head.MatchLastAndBr(ILCode.Brtrue, out trueLabel, out condExpr, out falseLabel) && + labelGlobalRefCount[trueLabel] == 1 && + labelGlobalRefCount[falseLabel] == 1 && + ((labelToBasicBlock[trueLabel].MatchSingleAndBr(ILCode.Stloc, out trueLocVar, out trueExpr, out trueFall) && + labelToBasicBlock[falseLabel].MatchSingleAndBr(ILCode.Stloc, out falseLocVar, out falseExpr, out falseFall) && + trueLocVar == falseLocVar && trueFall == falseFall) || + (labelToBasicBlock[trueLabel].MatchSingle(ILCode.Ret, out unused, out trueExpr) && + labelToBasicBlock[falseLabel].MatchSingle(ILCode.Ret, out unused, out falseExpr))) && + body.Contains(labelToBasicBlock[trueLabel]) && + body.Contains(labelToBasicBlock[falseLabel]) + ) + { + bool isStloc = trueLocVar != null; + ILCode opCode = isStloc ? ILCode.Stloc : ILCode.Ret; + TypeReference retType = isStloc ? trueLocVar.Type : context.CurrentMethod.ReturnType; + bool retTypeIsBoolean = TypeAnalysis.IsBoolean(retType); + int leftBoolVal; + int rightBoolVal; + ILExpression newExpr; + // a ? true:false is equivalent to a + // a ? false:true is equivalent to !a + // a ? true : b is equivalent to a || b + // a ? b : true is equivalent to !a || b + // a ? b : false is equivalent to a && b + // a ? false : b is equivalent to !a && b + if (retTypeIsBoolean && + trueExpr.Match(ILCode.Ldc_I4, out leftBoolVal) && + falseExpr.Match(ILCode.Ldc_I4, out rightBoolVal) && + ((leftBoolVal != 0 && rightBoolVal == 0) || (leftBoolVal == 0 && rightBoolVal != 0)) + ) + { + // It can be expressed as trivilal expression + if (leftBoolVal != 0) { + newExpr = condExpr; + } else { + newExpr = new ILExpression(ILCode.LogicNot, null, condExpr) { InferredType = typeSystem.Boolean }; + } + } else if ((retTypeIsBoolean || TypeAnalysis.IsBoolean(falseExpr.InferredType)) && trueExpr.Match(ILCode.Ldc_I4, out leftBoolVal) && (leftBoolVal == 0 || leftBoolVal == 1)) { + // It can be expressed as logical expression + if (leftBoolVal != 0) { + newExpr = MakeLeftAssociativeShortCircuit(ILCode.LogicOr, condExpr, falseExpr); + } else { + newExpr = MakeLeftAssociativeShortCircuit(ILCode.LogicAnd, new ILExpression(ILCode.LogicNot, null, condExpr), falseExpr); + } + } else if ((retTypeIsBoolean || TypeAnalysis.IsBoolean(trueExpr.InferredType)) && falseExpr.Match(ILCode.Ldc_I4, out rightBoolVal) && (rightBoolVal == 0 || rightBoolVal == 1)) { + // It can be expressed as logical expression + if (rightBoolVal != 0) { + newExpr = MakeLeftAssociativeShortCircuit(ILCode.LogicOr, new ILExpression(ILCode.LogicNot, null, condExpr), trueExpr); + } else { + newExpr = MakeLeftAssociativeShortCircuit(ILCode.LogicAnd, condExpr, trueExpr); + } + } else { + // Ternary operator tends to create long complicated return statements + if (opCode == ILCode.Ret) + return false; + + // Only simplify generated variables + if (opCode == ILCode.Stloc && !trueLocVar.IsGenerated) + return false; + + // Create ternary expression + newExpr = new ILExpression(ILCode.TernaryOp, null, condExpr, trueExpr, falseExpr); + } + + head.Body.RemoveTail(ILCode.Brtrue, ILCode.Br); + head.Body.Add(new ILExpression(opCode, trueLocVar, newExpr)); + if (isStloc) + head.Body.Add(new ILExpression(ILCode.Br, trueFall)); + + // Remove the old basic blocks + body.RemoveOrThrow(labelToBasicBlock[trueLabel]); + body.RemoveOrThrow(labelToBasicBlock[falseLabel]); + + return true; + } + return false; + } + + public bool SimplifyNullCoalescing(List<ILNode> body, ILBasicBlock head, int pos) + { + // ... + // v = ldloc(leftVar) + // brtrue(endBBLabel, ldloc(leftVar)) + // br(rightBBLabel) + // + // rightBBLabel: + // v = rightExpr + // br(endBBLabel) + // ... + // => + // ... + // v = NullCoalescing(ldloc(leftVar), rightExpr) + // br(endBBLabel) + + ILVariable v, v2; + ILExpression leftExpr, leftExpr2; + ILVariable leftVar; + ILLabel endBBLabel, endBBLabel2; + ILLabel rightBBLabel; + ILBasicBlock rightBB; + ILExpression rightExpr; + if (head.Body.Count >= 3 && + head.Body[head.Body.Count - 3].Match(ILCode.Stloc, out v, out leftExpr) && + leftExpr.Match(ILCode.Ldloc, out leftVar) && + head.MatchLastAndBr(ILCode.Brtrue, out endBBLabel, out leftExpr2, out rightBBLabel) && + leftExpr2.MatchLdloc(leftVar) && + labelToBasicBlock.TryGetValue(rightBBLabel, out rightBB) && + rightBB.MatchSingleAndBr(ILCode.Stloc, out v2, out rightExpr, out endBBLabel2) && + v == v2 && + endBBLabel == endBBLabel2 && + labelGlobalRefCount.GetOrDefault(rightBBLabel) == 1 && + body.Contains(rightBB) + ) + { + head.Body.RemoveTail(ILCode.Stloc, ILCode.Brtrue, ILCode.Br); + head.Body.Add(new ILExpression(ILCode.Stloc, v, new ILExpression(ILCode.NullCoalescing, null, leftExpr, rightExpr))); + head.Body.Add(new ILExpression(ILCode.Br, endBBLabel)); + + body.RemoveOrThrow(labelToBasicBlock[rightBBLabel]); + return true; + } + return false; + } + + public bool SimplifyShortCircuit(List<ILNode> body, ILBasicBlock head, int pos) + { + Debug.Assert(body.Contains(head)); + + ILExpression condExpr; + ILLabel trueLabel; + ILLabel falseLabel; + if(head.MatchLastAndBr(ILCode.Brtrue, out trueLabel, out condExpr, out falseLabel)) { + for (int pass = 0; pass < 2; pass++) { + + // On the second pass, swap labels and negate expression of the first branch + // It is slightly ugly, but much better then copy-pasting this whole block + ILLabel nextLabel = (pass == 0) ? trueLabel : falseLabel; + ILLabel otherLablel = (pass == 0) ? falseLabel : trueLabel; + bool negate = (pass == 1); + + ILBasicBlock nextBasicBlock = labelToBasicBlock[nextLabel]; + ILExpression nextCondExpr; + ILLabel nextTrueLablel; + ILLabel nextFalseLabel; + if (body.Contains(nextBasicBlock) && + nextBasicBlock != head && + labelGlobalRefCount[(ILLabel)nextBasicBlock.Body.First()] == 1 && + nextBasicBlock.MatchSingleAndBr(ILCode.Brtrue, out nextTrueLablel, out nextCondExpr, out nextFalseLabel) && + (otherLablel == nextFalseLabel || otherLablel == nextTrueLablel)) + { + // Create short cicuit branch + ILExpression logicExpr; + if (otherLablel == nextFalseLabel) { + logicExpr = MakeLeftAssociativeShortCircuit(ILCode.LogicAnd, negate ? new ILExpression(ILCode.LogicNot, null, condExpr) : condExpr, nextCondExpr); + } else { + logicExpr = MakeLeftAssociativeShortCircuit(ILCode.LogicOr, negate ? condExpr : new ILExpression(ILCode.LogicNot, null, condExpr), nextCondExpr); + } + head.Body.RemoveTail(ILCode.Brtrue, ILCode.Br); + head.Body.Add(new ILExpression(ILCode.Brtrue, nextTrueLablel, logicExpr)); + head.Body.Add(new ILExpression(ILCode.Br, nextFalseLabel)); + + // Remove the inlined branch from scope + body.RemoveOrThrow(nextBasicBlock); + + return true; + } + } + } + return false; + } + + public bool SimplifyCustomShortCircuit(List<ILNode> body, ILBasicBlock head, int pos) + { + Debug.Assert(body.Contains(head)); + + // --- looking for the following pattern --- + // stloc(targetVar, leftVar) + // brtrue(exitLabel, call(op_False, leftVar) + // br(followingBlock) + // + // FollowingBlock: + // stloc(targetVar, call(op_BitwiseAnd, leftVar, rightExpression)) + // br(exitLabel) + // --- + + if (head.Body.Count < 3) + return false; + + // looking for: + // stloc(targetVar, leftVar) + ILVariable targetVar; + ILExpression targetVarInitExpr; + if (!head.Body[head.Body.Count - 3].Match(ILCode.Stloc, out targetVar, out targetVarInitExpr)) + return false; + + ILVariable leftVar; + if (!targetVarInitExpr.Match(ILCode.Ldloc, out leftVar)) + return false; + + // looking for: + // brtrue(exitLabel, call(op_False, leftVar) + // br(followingBlock) + ILExpression callExpr; + ILLabel exitLabel; + ILLabel followingBlock; + if(!head.MatchLastAndBr(ILCode.Brtrue, out exitLabel, out callExpr, out followingBlock)) + return false; + + if (labelGlobalRefCount[followingBlock] > 1) + return false; + + MethodReference opFalse; + ILExpression opFalseArg; + if (!callExpr.Match(ILCode.Call, out opFalse, out opFalseArg)) + return false; + + // ignore operators other than op_False and op_True + if (opFalse.Name != "op_False" && opFalse.Name != "op_True") + return false; + + if (!opFalseArg.MatchLdloc(leftVar)) + return false; + + ILBasicBlock followingBasicBlock = labelToBasicBlock[followingBlock]; + + // FollowingBlock: + // stloc(targetVar, call(op_BitwiseAnd, leftVar, rightExpression)) + // br(exitLabel) + ILVariable _targetVar; + ILExpression opBitwiseCallExpr; + ILLabel _exitLabel; + if (!followingBasicBlock.MatchSingleAndBr(ILCode.Stloc, out _targetVar, out opBitwiseCallExpr, out _exitLabel)) + return false; + + if (_targetVar != targetVar || exitLabel != _exitLabel) + return false; + + MethodReference opBitwise; + ILExpression leftVarExpression; + ILExpression rightExpression; + if (!opBitwiseCallExpr.Match(ILCode.Call, out opBitwise, out leftVarExpression, out rightExpression)) + return false; + + if (!leftVarExpression.MatchLdloc(leftVar)) + return false; + + // ignore operators other than op_BitwiseAnd and op_BitwiseOr + if (opBitwise.Name != "op_BitwiseAnd" && opBitwise.Name != "op_BitwiseOr") + return false; + + // insert: + // stloc(targetVar, LogicAnd(C::op_BitwiseAnd, leftVar, rightExpression) + // br(exitLabel) + ILCode op = opBitwise.Name == "op_BitwiseAnd" ? ILCode.LogicAnd : ILCode.LogicOr; + + if (op == ILCode.LogicAnd && opFalse.Name != "op_False") + return false; + + if (op == ILCode.LogicOr && opFalse.Name != "op_True") + return false; + + ILExpression shortCircuitExpr = MakeLeftAssociativeShortCircuit(op, opFalseArg, rightExpression); + shortCircuitExpr.Operand = opBitwise; + + head.Body.RemoveTail(ILCode.Stloc, ILCode.Brtrue, ILCode.Br); + head.Body.Add(new ILExpression(ILCode.Stloc, targetVar, shortCircuitExpr)); + head.Body.Add(new ILExpression(ILCode.Br, exitLabel)); + body.Remove(followingBasicBlock); + + return true; + } + + ILExpression MakeLeftAssociativeShortCircuit(ILCode code, ILExpression left, ILExpression right) + { + // Assuming that the inputs are already left associative + if (right.Match(code)) { + // Find the leftmost logical expression + ILExpression current = right; + while(current.Arguments[0].Match(code)) + current = current.Arguments[0]; + current.Arguments[0] = new ILExpression(code, null, left, current.Arguments[0]) { InferredType = typeSystem.Boolean }; + return right; + } else { + return new ILExpression(code, null, left, right) { InferredType = typeSystem.Boolean }; + } + } + + public bool JoinBasicBlocks(List<ILNode> body, ILBasicBlock head, int pos) + { + ILLabel nextLabel; + ILBasicBlock nextBB; + if (!head.Body.ElementAtOrDefault(head.Body.Count - 2).IsConditionalControlFlow() && + head.Body.Last().Match(ILCode.Br, out nextLabel) && + labelGlobalRefCount[nextLabel] == 1 && + labelToBasicBlock.TryGetValue(nextLabel, out nextBB) && + body.Contains(nextBB) && + nextBB.Body.First() == nextLabel && + !nextBB.Body.OfType<ILTryCatchBlock>().Any() + ) + { + head.Body.RemoveTail(ILCode.Br); + nextBB.Body.RemoveAt(0); // Remove label + head.Body.AddRange(nextBB.Body); + + body.RemoveOrThrow(nextBB); + return true; + } + return false; + } + } +} diff --git a/ICSharpCode.Decompiler/ILAst/StateRange.cs b/ICSharpCode.Decompiler/ILAst/StateRange.cs new file mode 100644 index 00000000..ef27c498 --- /dev/null +++ b/ICSharpCode.Decompiler/ILAst/StateRange.cs @@ -0,0 +1,312 @@ +// 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 +{ + internal struct Interval + { + public readonly int Start, End; + + public Interval(int start, int end) + { + Debug.Assert(start <= end || (start == 0 && end == -1)); + Start = start; + End = end; + } + + public override string ToString() + { + return string.Format("({0} to {1})", Start, End); + } + } + + internal class StateRange + { + readonly List<Interval> data = new List<Interval>(); + + public StateRange() + { + } + + public StateRange(int start, int end) + { + 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); + } + + /// <summary> + /// Unions this state range with (other intersect (minVal to maxVal)) + /// </summary> + 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)); + } + } + + /// <summary> + /// Merges overlapping interval ranges. + /// </summary> + 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); + } + } + + internal enum StateRangeAnalysisMode + { + IteratorMoveNext, + IteratorDispose, + AsyncMoveNext + } + + internal class StateRangeAnalysis + { + readonly StateRangeAnalysisMode mode; + readonly FieldDefinition stateField; + internal DefaultDictionary<ILNode, StateRange> ranges; + SymbolicEvaluationContext evalContext; + + internal Dictionary<MethodDefinition, StateRange> finallyMethodToStateRange; // used only for IteratorDispose + + /// <summary> + /// Initializes the state range logic: + /// Clears 'ranges' and sets 'ranges[entryPoint]' to the full range (int.MinValue to int.MaxValue) + /// </summary> + public StateRangeAnalysis(ILNode entryPoint, StateRangeAnalysisMode mode, FieldDefinition stateField, ILVariable cachedStateVar = null) + { + this.mode = mode; + this.stateField = stateField; + if (mode == StateRangeAnalysisMode.IteratorDispose) { + finallyMethodToStateRange = new Dictionary<MethodDefinition, StateRange>(); + } + + ranges = new DefaultDictionary<ILNode, StateRange>(n => new StateRange()); + ranges[entryPoint] = new StateRange(int.MinValue, int.MaxValue); + evalContext = new SymbolicEvaluationContext(stateField); + if (cachedStateVar != null) + evalContext.AddStateVariable(cachedStateVar); + } + + public int AssignStateRanges(List<ILNode> 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]).AsBool(); + 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 || finallyMethodToStateRange.ContainsKey(mdef)) + throw new SymbolicAnalysisFailedException(); + finallyMethodToStateRange.Add(mdef, nodeRange); + break; + } else { + goto default; + } + default: + if (mode == StateRangeAnalysisMode.IteratorDispose) { + throw new SymbolicAnalysisFailedException(); + } else { + return i; + } + } + } + return bodyLength; + } + + public void EnsureLabelAtPos(List<ILNode> 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<ILNode> body, int pos, int bodyLength) + { + LabelRangeMapping result = new LabelRangeMapping(); + CreateLabelRangeMapping(body, pos, bodyLength, result, false); + return result; + } + + void CreateLabelRangeMapping(List<ILNode> 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<ILLabel, StateRange>(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; + } + } + } + } + } + + internal class LabelRangeMapping : List<KeyValuePair<ILLabel, StateRange>> {} +} diff --git a/ICSharpCode.Decompiler/ILAst/SymbolicExecution.cs b/ICSharpCode.Decompiler/ILAst/SymbolicExecution.cs new file mode 100644 index 00000000..76c57eb0 --- /dev/null +++ b/ICSharpCode.Decompiler/ILAst/SymbolicExecution.cs @@ -0,0 +1,157 @@ +// 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 +{ + /// <summary> + /// 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. + /// </summary> + internal class SymbolicAnalysisFailedException : Exception {} + + internal enum SymbolicValueType + { + /// <summary> + /// Unknown value + /// </summary> + Unknown, + /// <summary> + /// int: Constant (result of ldc.i4) + /// </summary> + IntegerConstant, + /// <summary> + /// int: State + Constant + /// </summary> + State, + /// <summary> + /// This pointer (result of ldarg.0) + /// </summary> + This, + /// <summary> + /// bool: State == Constant + /// </summary> + StateEquals, + /// <summary> + /// bool: State != Constant + /// </summary> + StateInEquals + } + + internal struct SymbolicValue + { + public readonly int Constant; + public readonly SymbolicValueType Type; + + public SymbolicValue(SymbolicValueType type, int constant = 0) + { + Type = type; + Constant = constant; + } + + public SymbolicValue AsBool() + { + if (Type == SymbolicValueType.State) { + // convert state integer to bool: + // if (state + c) = if (state + c != 0) = if (state != -c) + return new SymbolicValue(SymbolicValueType.StateInEquals, unchecked(-Constant)); + } + return this; + } + public override string ToString() + { + return string.Format("[SymbolicValue {0}: {1}]", Type, Constant); + } + } + + internal class SymbolicEvaluationContext + { + readonly FieldDefinition stateField; + readonly List<ILVariable> stateVariables = new List<ILVariable>(); + + 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]).AsBool(); + 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 new file mode 100644 index 00000000..1931d390 --- /dev/null +++ b/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs @@ -0,0 +1,1294 @@ +// 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 System.Linq; +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace ICSharpCode.Decompiler.ILAst +{ + /// <summary> + /// Assigns C# types to IL expressions. + /// </summary> + /// <remarks> + /// Types are inferred in a bidirectional manner: + /// The expected type flows from the outside to the inside, the actual inferred type flows from the inside to the outside. + /// </remarks> + public class TypeAnalysis + { + public static void Run(DecompilerContext context, ILBlock method) + { + TypeAnalysis ta = new TypeAnalysis(); + ta.context = context; + ta.module = context.CurrentMethod.Module; + ta.typeSystem = ta.module.TypeSystem; + ta.method = method; + ta.CreateDependencyGraph(method); + ta.IdentifySingleLoadVariables(); + ta.RunInference(); + } + + sealed class ExpressionToInfer + { + public ILExpression Expression; + + public bool Done; + + /// <summary> + /// Set for assignment expressions that should wait until the variable type is available + /// from the context where the variable is used. + /// </summary> + public ILVariable DependsOnSingleLoad; + + /// <summary> + /// The list variables that are read by this expression. + /// </summary> + public List<ILVariable> Dependencies = new List<ILVariable>(); + + public override string ToString() + { + if (Done) + return "[Done] " + Expression.ToString(); + else + return Expression.ToString(); + } + + } + + DecompilerContext context; + TypeSystem typeSystem; + ILBlock method; + ModuleDefinition module; + List<ExpressionToInfer> allExpressions = new List<ExpressionToInfer>(); + DefaultDictionary<ILVariable, List<ExpressionToInfer>> assignmentExpressions = new DefaultDictionary<ILVariable, List<ExpressionToInfer>>(_ => new List<ExpressionToInfer>()); + HashSet<ILVariable> singleLoadVariables = new HashSet<ILVariable>(); + + #region CreateDependencyGraph + /// <summary> + /// Creates the "ExpressionToInfer" instances (=nodes in dependency graph) + /// </summary> + /// <remarks> + /// We are using a dependency graph to ensure that expressions are analyzed in the correct order. + /// </remarks> + void CreateDependencyGraph(ILNode node) + { + ILCondition cond = node as ILCondition; + if (cond != null) { + cond.Condition.ExpectedType = typeSystem.Boolean; + } + ILWhileLoop loop = node as ILWhileLoop; + if (loop != null && loop.Condition != null) { + loop.Condition.ExpectedType = typeSystem.Boolean; + } + ILTryCatchBlock.CatchBlock catchBlock = node as ILTryCatchBlock.CatchBlock; + if (catchBlock != null && catchBlock.ExceptionVariable != null && catchBlock.ExceptionType != null && catchBlock.ExceptionVariable.Type == null) { + catchBlock.ExceptionVariable.Type = catchBlock.ExceptionType; + } + ILExpression expr = node as ILExpression; + if (expr != null) { + ExpressionToInfer expressionToInfer = new ExpressionToInfer(); + expressionToInfer.Expression = expr; + allExpressions.Add(expressionToInfer); + FindNestedAssignments(expr, expressionToInfer); + + if (expr.Code == ILCode.Stloc && ((ILVariable)expr.Operand).Type == null) + assignmentExpressions[(ILVariable)expr.Operand].Add(expressionToInfer); + return; + } + foreach (ILNode child in node.GetChildren()) { + CreateDependencyGraph(child); + } + } + + void FindNestedAssignments(ILExpression expr, ExpressionToInfer parent) + { + foreach (ILExpression arg in expr.Arguments) { + if (arg.Code == ILCode.Stloc) { + ExpressionToInfer expressionToInfer = new ExpressionToInfer(); + expressionToInfer.Expression = arg; + allExpressions.Add(expressionToInfer); + FindNestedAssignments(arg, expressionToInfer); + ILVariable v = (ILVariable)arg.Operand; + if (v.Type == null) { + assignmentExpressions[v].Add(expressionToInfer); + // the instruction that consumes the stloc result is handled as if it was reading the variable + parent.Dependencies.Add(v); + } + } else { + ILVariable v; + if (arg.Match(ILCode.Ldloc, out v) && v.Type == null) { + parent.Dependencies.Add(v); + } + FindNestedAssignments(arg, parent); + } + } + } + #endregion + + void IdentifySingleLoadVariables() + { + // Find all variables that are assigned to exactly a single time: + var q = from expr in allExpressions + from v in expr.Dependencies + group expr by v; + foreach (var g in q.ToArray()) { + ILVariable v = g.Key; + if (g.Count() == 1 && g.Single().Expression.GetSelfAndChildrenRecursive<ILExpression>().Count(e => e.Operand == v) == 1) { + singleLoadVariables.Add(v); + // Mark the assignments as dependent on the type from the single load: + foreach (var assignment in assignmentExpressions[v]) { + assignment.DependsOnSingleLoad = v; + } + } + } + } + + void RunInference() + { + int numberOfExpressionsAlreadyInferred = 0; + // Two flags that allow resolving cycles: + bool ignoreSingleLoadDependencies = false; + bool assignVariableTypesBasedOnPartialInformation = false; + while (numberOfExpressionsAlreadyInferred < allExpressions.Count) { + int oldCount = numberOfExpressionsAlreadyInferred; + foreach (ExpressionToInfer expr in allExpressions) { + if (!expr.Done && expr.Dependencies.TrueForAll(v => v.Type != null || singleLoadVariables.Contains(v)) + && (expr.DependsOnSingleLoad == null || expr.DependsOnSingleLoad.Type != null || ignoreSingleLoadDependencies)) + { + RunInference(expr.Expression); + expr.Done = true; + numberOfExpressionsAlreadyInferred++; + } + } + if (numberOfExpressionsAlreadyInferred == oldCount) { + if (ignoreSingleLoadDependencies) { + if (assignVariableTypesBasedOnPartialInformation) + throw new InvalidOperationException("Could not infer any expression"); + else + assignVariableTypesBasedOnPartialInformation = true; + } else { + // We have a cyclic dependency; we'll try if we can resolve it by ignoring single-load dependencies. + // This can happen if the variable was not actually assigned an expected type by the single-load instruction. + ignoreSingleLoadDependencies = true; + continue; + } + } else { + assignVariableTypesBasedOnPartialInformation = false; + ignoreSingleLoadDependencies = false; + } + // Now infer types for variables: + foreach (var pair in assignmentExpressions) { + ILVariable v = pair.Key; + if (v.Type == null && (assignVariableTypesBasedOnPartialInformation ? pair.Value.Any(e => e.Done) : pair.Value.All(e => e.Done))) { + TypeReference inferredType = null; + foreach (ExpressionToInfer expr in pair.Value) { + Debug.Assert(expr.Expression.Code == ILCode.Stloc); + ILExpression assignedValue = expr.Expression.Arguments.Single(); + if (assignedValue.InferredType != null) { + if (inferredType == null) { + inferredType = assignedValue.InferredType; + } else { + // pick the common base type + inferredType = TypeWithMoreInformation(inferredType, assignedValue.InferredType); + } + } + } + if (inferredType == null) + inferredType = typeSystem.Object; + v.Type = inferredType; + // Assign inferred type to all the assignments (in case they used different inferred types): + foreach (ExpressionToInfer expr in pair.Value) { + expr.Expression.InferredType = inferredType; + // re-infer if the expected type has changed + InferTypeForExpression(expr.Expression.Arguments.Single(), inferredType); + } + } + } + } + } + + void RunInference(ILExpression expr) + { + bool anyArgumentIsMissingExpectedType = expr.Arguments.Any(a => a.ExpectedType == null); + if (expr.InferredType == null || anyArgumentIsMissingExpectedType) + InferTypeForExpression(expr, expr.ExpectedType, forceInferChildren: anyArgumentIsMissingExpectedType); + foreach (var arg in expr.Arguments) { + if (arg.Code != ILCode.Stloc) { + RunInference(arg); + } + } + } + + /// <summary> + /// Infers the C# type of <paramref name="expr"/>. + /// </summary> + /// <param name="expr">The expression</param> + /// <param name="expectedType">The expected type of the expression</param> + /// <param name="forceInferChildren">Whether direct children should be inferred even if its not necessary. (does not apply to nested children!)</param> + /// <returns>The inferred type</returns> + TypeReference InferTypeForExpression(ILExpression expr, TypeReference expectedType, bool forceInferChildren = false) + { + if (expectedType != null && !IsSameType(expr.ExpectedType, expectedType)) { + expr.ExpectedType = expectedType; + if (expr.Code != ILCode.Stloc) // stloc is special case and never gets re-evaluated + forceInferChildren = true; + } + if (forceInferChildren || expr.InferredType == null) + expr.InferredType = DoInferTypeForExpression(expr, expectedType, forceInferChildren); + return expr.InferredType; + } + + TypeReference DoInferTypeForExpression(ILExpression expr, TypeReference expectedType, bool forceInferChildren = false) + { + switch (expr.Code) { + #region Logical operators + case ILCode.LogicNot: + if (forceInferChildren) { + InferTypeForExpression(expr.Arguments.Single(), typeSystem.Boolean); + } + return typeSystem.Boolean; + case ILCode.LogicAnd: + case ILCode.LogicOr: + // if Operand is set the logic and/or expression is a custom operator + // we can deal with it the same as a normal invocation. + if (expr.Operand != null) + goto case ILCode.Call; + if (forceInferChildren) { + InferTypeForExpression(expr.Arguments[0], typeSystem.Boolean); + InferTypeForExpression(expr.Arguments[1], typeSystem.Boolean); + } + return typeSystem.Boolean; + case ILCode.TernaryOp: + if (forceInferChildren) { + InferTypeForExpression(expr.Arguments[0], typeSystem.Boolean); + } + return InferBinaryArguments(expr.Arguments[1], expr.Arguments[2], expectedType, forceInferChildren); + case ILCode.NullCoalescing: + return InferBinaryArguments(expr.Arguments[0], expr.Arguments[1], expectedType, forceInferChildren); + #endregion + #region Variable load/store + case ILCode.Stloc: + { + ILVariable v = (ILVariable)expr.Operand; + if (forceInferChildren) { + // do not use 'expectedType' in here! + InferTypeForExpression(expr.Arguments.Single(), v.Type); + } + return v.Type; + } + case ILCode.Ldloc: + { + ILVariable v = (ILVariable)expr.Operand; + if (v.Type == null && singleLoadVariables.Contains(v)) { + v.Type = expectedType; + } + return v.Type; + } + case ILCode.Ldloca: + { + ILVariable v = (ILVariable)expr.Operand; + if (v.Type != null) + return new ByReferenceType(v.Type); + else + return null; + } + #endregion + #region Call / NewObj + case ILCode.Call: + case ILCode.Callvirt: + case ILCode.CallGetter: + case ILCode.CallvirtGetter: + case ILCode.CallSetter: + case ILCode.CallvirtSetter: + { + MethodReference method = (MethodReference)expr.Operand; + if (forceInferChildren) { + for (int i = 0; i < expr.Arguments.Count; i++) { + if (i == 0 && method.HasThis) { + InferTypeForExpression(expr.Arguments[0], MakeRefIfValueType(method.DeclaringType, expr.GetPrefix(ILCode.Constrained))); + } else { + InferTypeForExpression(expr.Arguments[i], SubstituteTypeArgs(method.Parameters[method.HasThis ? i - 1 : i].ParameterType, method)); + } + } + } + if (expr.Code == ILCode.CallSetter || expr.Code == ILCode.CallvirtSetter) { + return SubstituteTypeArgs(method.Parameters.Last().ParameterType, method); + } else { + return SubstituteTypeArgs(method.ReturnType, method); + } + } + case ILCode.Newobj: + { + MethodReference ctor = (MethodReference)expr.Operand; + if (forceInferChildren) { + for (int i = 0; i < ctor.Parameters.Count; i++) { + InferTypeForExpression(expr.Arguments[i], SubstituteTypeArgs(ctor.Parameters[i].ParameterType, ctor)); + } + } + return ctor.DeclaringType; + } + case ILCode.InitObject: + case ILCode.InitCollection: + return InferTypeForExpression(expr.Arguments[0], expectedType); + case ILCode.InitializedObject: + // expectedType should always be known due to the parent method call / property setter + Debug.Assert(expectedType != null); + return expectedType; + #endregion + #region Load/Store Fields + case ILCode.Ldfld: + if (forceInferChildren) { + InferTypeForExpression(expr.Arguments[0], MakeRefIfValueType(((FieldReference)expr.Operand).DeclaringType, expr.GetPrefix(ILCode.Constrained))); + } + return GetFieldType((FieldReference)expr.Operand); + case ILCode.Ldsfld: + return GetFieldType((FieldReference)expr.Operand); + case ILCode.Ldflda: + if (forceInferChildren) { + InferTypeForExpression(expr.Arguments[0], MakeRefIfValueType(((FieldReference)expr.Operand).DeclaringType, expr.GetPrefix(ILCode.Constrained))); + } + return new ByReferenceType(GetFieldType((FieldReference)expr.Operand)); + case ILCode.Ldsflda: + return new ByReferenceType(GetFieldType((FieldReference)expr.Operand)); + case ILCode.Stfld: + if (forceInferChildren) { + InferTypeForExpression(expr.Arguments[0], MakeRefIfValueType(((FieldReference)expr.Operand).DeclaringType, expr.GetPrefix(ILCode.Constrained))); + InferTypeForExpression(expr.Arguments[1], GetFieldType((FieldReference)expr.Operand)); + } + return GetFieldType((FieldReference)expr.Operand); + case ILCode.Stsfld: + if (forceInferChildren) + InferTypeForExpression(expr.Arguments[0], GetFieldType((FieldReference)expr.Operand)); + return GetFieldType((FieldReference)expr.Operand); + #endregion + #region Reference/Pointer instructions + case ILCode.Ldind_Ref: + return UnpackPointer(InferTypeForExpression(expr.Arguments[0], null)); + case ILCode.Stind_Ref: + if (forceInferChildren) { + TypeReference elementType = UnpackPointer(InferTypeForExpression(expr.Arguments[0], null)); + InferTypeForExpression(expr.Arguments[1], elementType); + } + return null; + case ILCode.Ldobj: + { + TypeReference type = (TypeReference)expr.Operand; + var argType = InferTypeForExpression(expr.Arguments[0], null); + if (argType is PointerType || argType is ByReferenceType) { + var elementType = ((TypeSpecification)argType).ElementType; + int infoAmount = GetInformationAmount(elementType); + if (infoAmount == 1 && GetInformationAmount(type) == 8) { + // A bool can be loaded from both bytes and sbytes. + type = elementType; + } + if (infoAmount >= 8 && infoAmount <= 64 && infoAmount == GetInformationAmount(type)) { + // An integer can be loaded as another integer of the same size. + // For integers smaller than 32 bit, the signs must match (as loading performs sign extension) + bool? elementTypeIsSigned = IsSigned(elementType); + bool? typeIsSigned = IsSigned(type); + if (elementTypeIsSigned != null && typeIsSigned != null) { + if (infoAmount >= 32 || elementTypeIsSigned == typeIsSigned) + type = elementType; + } + } + } + if (argType is PointerType) + InferTypeForExpression(expr.Arguments[0], new PointerType(type)); + else + InferTypeForExpression(expr.Arguments[0], new ByReferenceType(type)); + return type; + } + case ILCode.Stobj: + { + TypeReference operandType = (TypeReference)expr.Operand; + TypeReference pointerType = InferTypeForExpression(expr.Arguments[0], new ByReferenceType(operandType)); + TypeReference elementType; + if (pointerType is PointerType) + elementType = ((PointerType)pointerType).ElementType; + else if (pointerType is ByReferenceType) + elementType = ((ByReferenceType)pointerType).ElementType; + else + elementType = null; + if (elementType != null) { + // An integer can be stored in any other integer of the same size. + int infoAmount = GetInformationAmount(elementType); + if (infoAmount == 1 && GetInformationAmount(operandType) == 8) + operandType = elementType; + else if (infoAmount == GetInformationAmount(operandType) && IsSigned(elementType) != null && IsSigned(operandType) != null) + operandType = elementType; + } + if (forceInferChildren) { + if (pointerType is PointerType) + InferTypeForExpression(expr.Arguments[0], new PointerType(operandType)); + else if (!IsSameType(operandType, expr.Operand as TypeReference)) + InferTypeForExpression(expr.Arguments[0], new ByReferenceType(operandType)); + InferTypeForExpression(expr.Arguments[1], operandType); + } + return operandType; + } + case ILCode.Initobj: + return null; + case ILCode.DefaultValue: + return (TypeReference)expr.Operand; + case ILCode.Localloc: + if (forceInferChildren) { + InferTypeForExpression(expr.Arguments[0], null); + } + if (expectedType is PointerType) + return expectedType; + else + return typeSystem.IntPtr; + case ILCode.Sizeof: + return typeSystem.Int32; + case ILCode.PostIncrement: + case ILCode.PostIncrement_Ovf: + case ILCode.PostIncrement_Ovf_Un: + { + TypeReference elementType = UnpackPointer(InferTypeForExpression(expr.Arguments[0], null)); + if (forceInferChildren && elementType != null) { + // Assign expected type to the child expression + InferTypeForExpression(expr.Arguments[0], new ByReferenceType(elementType)); + } + return elementType; + } + case ILCode.Mkrefany: + if (forceInferChildren) { + InferTypeForExpression(expr.Arguments[0], (TypeReference)expr.Operand); + } + return typeSystem.TypedReference; + case ILCode.Refanytype: + if (forceInferChildren) { + InferTypeForExpression(expr.Arguments[0], typeSystem.TypedReference); + } + return new TypeReference("System", "RuntimeTypeHandle", module, module.TypeSystem.Corlib, true); + case ILCode.Refanyval: + if (forceInferChildren) { + InferTypeForExpression(expr.Arguments[0], typeSystem.TypedReference); + } + return new ByReferenceType((TypeReference)expr.Operand); + case ILCode.AddressOf: + { + TypeReference t = InferTypeForExpression(expr.Arguments[0], UnpackPointer(expectedType)); + return t != null ? new ByReferenceType(t) : null; + } + case ILCode.ValueOf: + return GetNullableTypeArgument(InferTypeForExpression(expr.Arguments[0], CreateNullableType(expectedType))); + case ILCode.NullableOf: + return CreateNullableType(InferTypeForExpression(expr.Arguments[0], GetNullableTypeArgument(expectedType))); + #endregion + #region Arithmetic instructions + case ILCode.Not: // bitwise complement + case ILCode.Neg: + return InferTypeForExpression(expr.Arguments.Single(), expectedType); + case ILCode.Add: + return InferArgumentsInAddition(expr, null, expectedType); + case ILCode.Sub: + return InferArgumentsInSubtraction(expr, null, expectedType); + case ILCode.Mul: + case ILCode.Or: + case ILCode.And: + case ILCode.Xor: + return InferArgumentsInBinaryOperator(expr, null, expectedType); + case ILCode.Add_Ovf: + return InferArgumentsInAddition(expr, true, expectedType); + case ILCode.Sub_Ovf: + return InferArgumentsInSubtraction(expr, true, expectedType); + case ILCode.Mul_Ovf: + case ILCode.Div: + case ILCode.Rem: + return InferArgumentsInBinaryOperator(expr, true, expectedType); + case ILCode.Add_Ovf_Un: + return InferArgumentsInAddition(expr, false, expectedType); + case ILCode.Sub_Ovf_Un: + return InferArgumentsInSubtraction(expr, false, expectedType); + case ILCode.Mul_Ovf_Un: + case ILCode.Div_Un: + case ILCode.Rem_Un: + return InferArgumentsInBinaryOperator(expr, false, expectedType); + case ILCode.Shl: + if (forceInferChildren) + InferTypeForExpression(expr.Arguments[1], typeSystem.Int32); + if (expectedType != null && ( + expectedType.MetadataType == MetadataType.Int32 || expectedType.MetadataType == MetadataType.UInt32 || + expectedType.MetadataType == MetadataType.Int64 || expectedType.MetadataType == MetadataType.UInt64) + ) + return NumericPromotion(InferTypeForExpression(expr.Arguments[0], expectedType)); + else + return NumericPromotion(InferTypeForExpression(expr.Arguments[0], null)); + case ILCode.Shr: + case ILCode.Shr_Un: + { + if (forceInferChildren) + InferTypeForExpression(expr.Arguments[1], typeSystem.Int32); + TypeReference type = NumericPromotion(InferTypeForExpression(expr.Arguments[0], null)); + if (type == null) + return null; + TypeReference expectedInputType = null; + switch (type.MetadataType) { + case MetadataType.Int32: + if (expr.Code == ILCode.Shr_Un) + expectedInputType = typeSystem.UInt32; + break; + case MetadataType.UInt32: + if (expr.Code == ILCode.Shr) + expectedInputType = typeSystem.Int32; + break; + case MetadataType.Int64: + if (expr.Code == ILCode.Shr_Un) + expectedInputType = typeSystem.UInt64; + break; + case MetadataType.UInt64: + if (expr.Code == ILCode.Shr) + expectedInputType = typeSystem.UInt64; + break; + } + if (expectedInputType != null) { + InferTypeForExpression(expr.Arguments[0], expectedInputType); + return expectedInputType; + } else { + return type; + } + } + case ILCode.CompoundAssignment: + { + var op = expr.Arguments[0]; + if (op.Code == ILCode.NullableOf) op = op.Arguments[0].Arguments[0]; + var varType = InferTypeForExpression(op.Arguments[0], null); + if (forceInferChildren) { + InferTypeForExpression(expr.Arguments[0], varType); + } + return varType; + } + #endregion + #region Constant loading instructions + case ILCode.Ldnull: + return typeSystem.Object; + case ILCode.Ldstr: + return typeSystem.String; + case ILCode.Ldftn: + case ILCode.Ldvirtftn: + return typeSystem.IntPtr; + case ILCode.Ldc_I4: + if (IsBoolean(expectedType) && ((int)expr.Operand == 0 || (int)expr.Operand == 1)) + return typeSystem.Boolean; + if (expectedType is PointerType && (int)expr.Operand == 0) + return expectedType; + if (IsIntegerOrEnum(expectedType) && OperandFitsInType(expectedType, (int)expr.Operand)) + return expectedType; + else + return typeSystem.Int32; + case ILCode.Ldc_I8: + if (expectedType is PointerType && (long)expr.Operand == 0) + return expectedType; + if (IsIntegerOrEnum(expectedType) && GetInformationAmount(expectedType) >= NativeInt) + return expectedType; + else + return typeSystem.Int64; + case ILCode.Ldc_R4: + return typeSystem.Single; + case ILCode.Ldc_R8: + return typeSystem.Double; + case ILCode.Ldc_Decimal: + return new TypeReference("System", "Decimal", module, module.TypeSystem.Corlib, true); + case ILCode.Ldtoken: + if (expr.Operand is TypeReference) + return new TypeReference("System", "RuntimeTypeHandle", module, module.TypeSystem.Corlib, true); + else if (expr.Operand is FieldReference) + return new TypeReference("System", "RuntimeFieldHandle", module, module.TypeSystem.Corlib, true); + else + return new TypeReference("System", "RuntimeMethodHandle", module, module.TypeSystem.Corlib, true); + case ILCode.Arglist: + return new TypeReference("System", "RuntimeArgumentHandle", module, module.TypeSystem.Corlib, true); + #endregion + #region Array instructions + case ILCode.Newarr: + if (forceInferChildren) { + var lengthType = InferTypeForExpression(expr.Arguments.Single(), null); + if (lengthType == typeSystem.IntPtr) { + lengthType = typeSystem.Int64; + } else if (lengthType == typeSystem.UIntPtr) { + lengthType = typeSystem.UInt64; + } else if (lengthType != typeSystem.UInt32 && lengthType != typeSystem.Int64 && lengthType != typeSystem.UInt64) { + lengthType = typeSystem.Int32; + } + if (forceInferChildren) { + InferTypeForExpression(expr.Arguments.Single(), lengthType); + } + } + return new ArrayType((TypeReference)expr.Operand); + case ILCode.InitArray: + var operandAsArrayType = (ArrayType)expr.Operand; + if (forceInferChildren) + { + foreach (ILExpression arg in expr.Arguments) + InferTypeForExpression(arg, operandAsArrayType.ElementType); + } + return operandAsArrayType; + case ILCode.Ldlen: + return typeSystem.Int32; + case ILCode.Ldelem_U1: + case ILCode.Ldelem_U2: + case ILCode.Ldelem_U4: + case ILCode.Ldelem_I1: + case ILCode.Ldelem_I2: + case ILCode.Ldelem_I4: + case ILCode.Ldelem_I8: + case ILCode.Ldelem_R4: + case ILCode.Ldelem_R8: + case ILCode.Ldelem_I: + case ILCode.Ldelem_Ref: + { + ArrayType arrayType = InferTypeForExpression(expr.Arguments[0], null) as ArrayType; + if (forceInferChildren) { + InferTypeForExpression(expr.Arguments[1], typeSystem.Int32); + } + return arrayType != null ? arrayType.ElementType : null; + } + case ILCode.Ldelem_Any: + if (forceInferChildren) { + InferTypeForExpression(expr.Arguments[1], typeSystem.Int32); + } + return (TypeReference)expr.Operand; + case ILCode.Ldelema: + { + ArrayType arrayType = InferTypeForExpression(expr.Arguments[0], null) as ArrayType; + if (forceInferChildren) + InferTypeForExpression(expr.Arguments[1], typeSystem.Int32); + return arrayType != null ? new ByReferenceType(arrayType.ElementType) : null; + } + case ILCode.Stelem_I: + case ILCode.Stelem_I1: + case ILCode.Stelem_I2: + case ILCode.Stelem_I4: + case ILCode.Stelem_I8: + case ILCode.Stelem_R4: + case ILCode.Stelem_R8: + case ILCode.Stelem_Ref: + case ILCode.Stelem_Any: + { + ArrayType arrayType = InferTypeForExpression(expr.Arguments[0], null) as ArrayType; + if (forceInferChildren) { + InferTypeForExpression(expr.Arguments[1], typeSystem.Int32); + if (arrayType != null) { + InferTypeForExpression(expr.Arguments[2], arrayType.ElementType); + } + } + return arrayType != null ? arrayType.ElementType : null; + } + #endregion + #region Conversion instructions + case ILCode.Conv_I1: + case ILCode.Conv_Ovf_I1: + case ILCode.Conv_Ovf_I1_Un: + return HandleConversion(8, true, expr.Arguments[0], expectedType, typeSystem.SByte); + case ILCode.Conv_I2: + case ILCode.Conv_Ovf_I2: + case ILCode.Conv_Ovf_I2_Un: + return HandleConversion(16, true, expr.Arguments[0], expectedType, typeSystem.Int16); + case ILCode.Conv_I4: + case ILCode.Conv_Ovf_I4: + case ILCode.Conv_Ovf_I4_Un: + return HandleConversion(32, true, expr.Arguments[0], expectedType, typeSystem.Int32); + case ILCode.Conv_I8: + case ILCode.Conv_Ovf_I8: + case ILCode.Conv_Ovf_I8_Un: + return HandleConversion(64, true, expr.Arguments[0], expectedType, typeSystem.Int64); + case ILCode.Conv_U1: + case ILCode.Conv_Ovf_U1: + case ILCode.Conv_Ovf_U1_Un: + return HandleConversion(8, false, expr.Arguments[0], expectedType, typeSystem.Byte); + case ILCode.Conv_U2: + case ILCode.Conv_Ovf_U2: + case ILCode.Conv_Ovf_U2_Un: + return HandleConversion(16, false, expr.Arguments[0], expectedType, typeSystem.UInt16); + case ILCode.Conv_U4: + case ILCode.Conv_Ovf_U4: + case ILCode.Conv_Ovf_U4_Un: + return HandleConversion(32, false, expr.Arguments[0], expectedType, typeSystem.UInt32); + case ILCode.Conv_U8: + case ILCode.Conv_Ovf_U8: + case ILCode.Conv_Ovf_U8_Un: + return HandleConversion(64, false, expr.Arguments[0], expectedType, typeSystem.UInt64); + case ILCode.Conv_I: + case ILCode.Conv_Ovf_I: + case ILCode.Conv_Ovf_I_Un: + return HandleConversion(NativeInt, true, expr.Arguments[0], expectedType, typeSystem.IntPtr); + case ILCode.Conv_U: + case ILCode.Conv_Ovf_U: + case ILCode.Conv_Ovf_U_Un: + return HandleConversion(NativeInt, false, expr.Arguments[0], expectedType, typeSystem.UIntPtr); + case ILCode.Conv_R4: + if (forceInferChildren) { + InferTypeForExpression(expr.Arguments[0], typeSystem.Single); + } + return typeSystem.Single; + case ILCode.Conv_R8: + if (forceInferChildren) { + InferTypeForExpression(expr.Arguments[0], typeSystem.Double); + } + return typeSystem.Double; + case ILCode.Conv_R_Un: + return (expectedType != null && expectedType.MetadataType == MetadataType.Single) ? typeSystem.Single : typeSystem.Double; + case ILCode.Castclass: + case ILCode.Unbox_Any: + return (TypeReference)expr.Operand; + case ILCode.Unbox: + return new ByReferenceType((TypeReference)expr.Operand); + case ILCode.Isinst: + { + // isinst performs the equivalent of a cast only for reference types; + // value types still need to be unboxed after an isinst instruction + TypeReference tr = (TypeReference)expr.Operand; + return tr.IsValueType ? typeSystem.Object : tr; + } + case ILCode.Box: + { + var tr = (TypeReference)expr.Operand; + if (forceInferChildren) + InferTypeForExpression(expr.Arguments.Single(), tr); + return tr.IsValueType ? typeSystem.Object : tr; + } + #endregion + #region Comparison instructions + case ILCode.Ceq: + case ILCode.Cne: + if (forceInferChildren) + InferArgumentsInBinaryOperator(expr, null, null); + return typeSystem.Boolean; + case ILCode.Clt: + case ILCode.Cgt: + case ILCode.Cle: + case ILCode.Cge: + if (forceInferChildren) + InferArgumentsInBinaryOperator(expr, true, null); + return typeSystem.Boolean; + case ILCode.Clt_Un: + case ILCode.Cgt_Un: + case ILCode.Cle_Un: + case ILCode.Cge_Un: + if (forceInferChildren) + InferArgumentsInBinaryOperator(expr, false, null); + return typeSystem.Boolean; + #endregion + #region Branch instructions + case ILCode.Brtrue: + if (forceInferChildren) + InferTypeForExpression(expr.Arguments.Single(), typeSystem.Boolean); + return null; + case ILCode.Br: + case ILCode.Leave: + case ILCode.Endfinally: + case ILCode.Switch: + case ILCode.Throw: + case ILCode.Rethrow: + case ILCode.LoopOrSwitchBreak: + case ILCode.LoopContinue: + case ILCode.YieldBreak: + return null; + case ILCode.Ret: + 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) { + GenericInstanceType genericType = context.CurrentMethod.ReturnType as GenericInstanceType; + if (genericType != null) { // IEnumerable<T> or IEnumerator<T> + InferTypeForExpression(expr.Arguments[0], genericType.GenericArguments[0]); + } else { // non-generic IEnumerable or IEnumerator + InferTypeForExpression(expr.Arguments[0], typeSystem.Object); + } + } + return null; + case ILCode.Await: + { + TypeReference taskType = InferTypeForExpression(expr.Arguments[0], null); + if (taskType != null && taskType.Name == "Task`1" && taskType.IsGenericInstance && taskType.Namespace == "System.Threading.Tasks") { + return ((GenericInstanceType)taskType).GenericArguments[0]; + } + return null; + } + #endregion + case ILCode.Pop: + return null; + case ILCode.Wrap: + case ILCode.Dup: + { + var arg = expr.Arguments.Single(); + return arg.ExpectedType = InferTypeForExpression(arg, expectedType); + } + default: + Debug.WriteLine("Type Inference: Can't handle " + expr.Code.GetName()); + return null; + } + } + + /// <summary> + /// Wraps 'type' in a ByReferenceType if it is a value type. If a constrained prefix is specified, + /// returns the constrained type wrapped in a ByReferenceType. + /// </summary> + TypeReference MakeRefIfValueType(TypeReference type, ILExpressionPrefix constrainedPrefix) + { + if (constrainedPrefix != null) + return new ByReferenceType((TypeReference)constrainedPrefix.Operand); + if (type.IsValueType) + return new ByReferenceType(type); + else + return type; + } + + /// <summary> + /// Promotes primitive types smaller than int32 to int32. + /// </summary> + /// <remarks> + /// Always promotes to signed int32. + /// </remarks> + TypeReference NumericPromotion(TypeReference type) + { + if (type == null) + return null; + switch (type.MetadataType) { + case MetadataType.SByte: + case MetadataType.Int16: + case MetadataType.Byte: + case MetadataType.UInt16: + return typeSystem.Int32; + default: + return type; + } + } + + TypeReference HandleConversion(int targetBitSize, bool targetSigned, ILExpression arg, TypeReference expectedType, TypeReference targetType) + { + if (targetBitSize >= NativeInt && expectedType is PointerType) { + InferTypeForExpression(arg, expectedType); + return expectedType; + } + TypeReference argType = InferTypeForExpression(arg, null); + if (targetBitSize >= NativeInt && argType is ByReferenceType) { + // conv instructions on managed references mean that the GC should stop tracking them, so they become pointers: + PointerType ptrType = new PointerType(((ByReferenceType)argType).ElementType); + InferTypeForExpression(arg, ptrType); + return ptrType; + } else if (targetBitSize >= NativeInt && argType is PointerType) { + return argType; + } + TypeReference resultType = (GetInformationAmount(expectedType) == targetBitSize && IsSigned(expectedType) == targetSigned) ? expectedType : targetType; + arg.ExpectedType = resultType; // store the expected type in the argument so that AstMethodBodyBuilder will insert a cast + return resultType; + } + + public static TypeReference GetFieldType(FieldReference fieldReference) + { + return SubstituteTypeArgs(UnpackModifiers(fieldReference.FieldType), fieldReference); + } + + public static TypeReference SubstituteTypeArgs(TypeReference type, MemberReference member) + { + if (type is TypeSpecification) { + ArrayType arrayType = type as ArrayType; + if (arrayType != null) { + TypeReference elementType = SubstituteTypeArgs(arrayType.ElementType, member); + if (elementType != arrayType.ElementType) { + ArrayType newArrayType = new ArrayType(elementType); + newArrayType.Dimensions.Clear(); // remove the single dimension that Cecil adds by default + foreach (ArrayDimension d in arrayType.Dimensions) + newArrayType.Dimensions.Add(d); + return newArrayType; + } else { + return type; + } + } + ByReferenceType refType = type as ByReferenceType; + if (refType != null) { + TypeReference elementType = SubstituteTypeArgs(refType.ElementType, member); + return elementType != refType.ElementType ? new ByReferenceType(elementType) : type; + } + GenericInstanceType giType = type as GenericInstanceType; + if (giType != null) { + GenericInstanceType newType = new GenericInstanceType(giType.ElementType); + bool isChanged = false; + for (int i = 0; i < giType.GenericArguments.Count; i++) { + newType.GenericArguments.Add(SubstituteTypeArgs(giType.GenericArguments[i], member)); + isChanged |= newType.GenericArguments[i] != giType.GenericArguments[i]; + } + return isChanged ? newType : type; + } + OptionalModifierType optmodType = type as OptionalModifierType; + if (optmodType != null) { + TypeReference elementType = SubstituteTypeArgs(optmodType.ElementType, member); + return elementType != optmodType.ElementType ? new OptionalModifierType(optmodType.ModifierType, elementType) : type; + } + RequiredModifierType reqmodType = type as RequiredModifierType; + if (reqmodType != null) { + TypeReference elementType = SubstituteTypeArgs(reqmodType.ElementType, member); + return elementType != reqmodType.ElementType ? new RequiredModifierType(reqmodType.ModifierType, elementType) : type; + } + PointerType ptrType = type as PointerType; + if (ptrType != null) { + TypeReference elementType = SubstituteTypeArgs(ptrType.ElementType, member); + return elementType != ptrType.ElementType ? new PointerType(elementType) : type; + } + } + GenericParameter gp = type as GenericParameter; + if (gp != null) { + if (member.DeclaringType is ArrayType) { + return ((ArrayType)member.DeclaringType).ElementType; + } else if (gp.Owner.GenericParameterType == GenericParameterType.Method) { + return ((GenericInstanceMethod)member).GenericArguments[gp.Position]; + } else { + return ((GenericInstanceType)member.DeclaringType).GenericArguments[gp.Position]; + } + } + return type; + } + + static TypeReference UnpackPointer(TypeReference pointerOrManagedReference) + { + ByReferenceType refType = pointerOrManagedReference as ByReferenceType; + if (refType != null) + return refType.ElementType; + PointerType ptrType = pointerOrManagedReference as PointerType; + if (ptrType != null) + return ptrType.ElementType; + return null; + } + + internal static TypeReference UnpackModifiers(TypeReference type) + { + while (type is OptionalModifierType || type is RequiredModifierType) + type = ((TypeSpecification)type).ElementType; + return type; + } + + static TypeReference GetNullableTypeArgument(TypeReference type) + { + var t = type as GenericInstanceType; + return IsNullableType(t) ? t.GenericArguments[0] : type; + } + + GenericInstanceType CreateNullableType(TypeReference type) + { + if (type == null) return null; + var t = new GenericInstanceType(new TypeReference("System", "Nullable`1", module, module.TypeSystem.Corlib, true)); + t.GenericArguments.Add(type); + return t; + } + + TypeReference InferArgumentsInBinaryOperator(ILExpression expr, bool? isSigned, TypeReference expectedType) + { + return InferBinaryArguments(expr.Arguments[0], expr.Arguments[1], expectedType); + } + + TypeReference InferArgumentsInAddition(ILExpression expr, bool? isSigned, TypeReference expectedType) + { + ILExpression left = expr.Arguments[0]; + ILExpression right = expr.Arguments[1]; + TypeReference leftPreferred = DoInferTypeForExpression(left, expectedType); + if (leftPreferred is PointerType) { + left.InferredType = left.ExpectedType = leftPreferred; + InferTypeForExpression(right, null); + return leftPreferred; + } + if (IsEnum(leftPreferred)) { + //E+U=E + left.InferredType = left.ExpectedType = leftPreferred; + InferTypeForExpression(right, GetEnumUnderlyingType(leftPreferred)); + return leftPreferred; + } + TypeReference rightPreferred = DoInferTypeForExpression(right, expectedType); + if (rightPreferred is PointerType) { + InferTypeForExpression(left, null); + right.InferredType = right.ExpectedType = rightPreferred; + return rightPreferred; + } + if (IsEnum(rightPreferred)) { + //U+E=E + right.InferredType = right.ExpectedType = rightPreferred; + InferTypeForExpression(left, GetEnumUnderlyingType(rightPreferred)); + return rightPreferred; + } + return InferBinaryArguments(left, right, expectedType, leftPreferred: leftPreferred, rightPreferred: rightPreferred); + } + + TypeReference InferArgumentsInSubtraction(ILExpression expr, bool? isSigned, TypeReference expectedType) + { + ILExpression left = expr.Arguments[0]; + ILExpression right = expr.Arguments[1]; + TypeReference leftPreferred = DoInferTypeForExpression(left, expectedType); + if (leftPreferred is PointerType) { + left.InferredType = left.ExpectedType = leftPreferred; + InferTypeForExpression(right, null); + return leftPreferred; + } + if (IsEnum(leftPreferred)) { + if (expectedType != null && IsEnum(expectedType)) { + // E-U=E + left.InferredType = left.ExpectedType = leftPreferred; + InferTypeForExpression(right, GetEnumUnderlyingType(leftPreferred)); + return leftPreferred; + } else { + // E-E=U + left.InferredType = left.ExpectedType = leftPreferred; + InferTypeForExpression(right, leftPreferred); + return GetEnumUnderlyingType(leftPreferred); + } + } + return InferBinaryArguments(left, right, expectedType, leftPreferred: leftPreferred); + } + + TypeReference InferBinaryArguments(ILExpression left, ILExpression right, TypeReference expectedType, bool forceInferChildren = false, TypeReference leftPreferred = null, TypeReference rightPreferred = null) + { + if (leftPreferred == null) leftPreferred = DoInferTypeForExpression(left, expectedType, forceInferChildren); + if (rightPreferred == null) rightPreferred = DoInferTypeForExpression(right, expectedType, forceInferChildren); + if (IsSameType(leftPreferred, rightPreferred)) { + return left.InferredType = right.InferredType = left.ExpectedType = right.ExpectedType = leftPreferred; + } else if (IsSameType(rightPreferred, DoInferTypeForExpression(left, rightPreferred, forceInferChildren))) { + return left.InferredType = right.InferredType = left.ExpectedType = right.ExpectedType = rightPreferred; + } else if (IsSameType(leftPreferred, DoInferTypeForExpression(right, leftPreferred, forceInferChildren))) { + // re-infer the left expression with the preferred type to reset any conflicts caused by the rightPreferred type + DoInferTypeForExpression(left, leftPreferred, forceInferChildren); + return left.InferredType = right.InferredType = left.ExpectedType = right.ExpectedType = leftPreferred; + } else { + left.ExpectedType = right.ExpectedType = TypeWithMoreInformation(leftPreferred, rightPreferred); + left.InferredType = DoInferTypeForExpression(left, left.ExpectedType, forceInferChildren); + right.InferredType = DoInferTypeForExpression(right, right.ExpectedType, forceInferChildren); + return left.ExpectedType; + } + } + + TypeReference TypeWithMoreInformation(TypeReference leftPreferred, TypeReference rightPreferred) + { + int left = GetInformationAmount(leftPreferred); + int right = GetInformationAmount(rightPreferred); + if (left < right) { + return rightPreferred; + } else if (left > right) { + return leftPreferred; + } else { + // TODO + return leftPreferred; + } + } + + /// <summary> + /// Information amount used for IntPtr. + /// </summary> + public const int NativeInt = 33; // treat native int as between int32 and int64 + + /// <summary> + /// Gets the underlying type, if the specified type is an enum. + /// Otherwise, returns null. + /// </summary> + public static TypeReference GetEnumUnderlyingType(TypeReference enumType) + { + // unfortunately we cannot rely on enumType.IsValueType here - it's not set when the instruction operand is a typeref (as opposed to a typespec) + if (enumType != null && !IsArrayPointerOrReference(enumType)) { + // value type might be an enum + TypeDefinition typeDef = enumType.Resolve() as TypeDefinition; + if (typeDef != null && typeDef.IsEnum) { + return typeDef.Fields.Single(f => !f.IsStatic).FieldType; + } + } + return null; + } + + public static int GetInformationAmount(TypeReference type) + { + type = GetEnumUnderlyingType(type) ?? type; + if (type == null) + return 0; + switch (type.MetadataType) { + case MetadataType.Void: + return 0; + case MetadataType.Boolean: + return 1; + case MetadataType.SByte: + case MetadataType.Byte: + return 8; + case MetadataType.Char: + case MetadataType.Int16: + case MetadataType.UInt16: + return 16; + case MetadataType.Int32: + case MetadataType.UInt32: + case MetadataType.Single: + return 32; + case MetadataType.Int64: + case MetadataType.UInt64: + case MetadataType.Double: + return 64; + case MetadataType.IntPtr: + case MetadataType.UIntPtr: + return NativeInt; + default: + return 100; // we consider structs/objects to have more information than any primitives + } + } + + public static bool IsBoolean(TypeReference type) + { + return type != null && type.MetadataType == MetadataType.Boolean; + } + + public static bool IsIntegerOrEnum(TypeReference type) + { + return IsSigned(type) != null; + } + + public static bool IsEnum(TypeReference type) + { + // Arrays/Pointers/ByReference resolve to their element type, but we don't want to consider those to be enums + // However, GenericInstanceTypes, ModOpts etc. should be considered enums. + if (type == null || IsArrayPointerOrReference(type)) + return false; + // unfortunately we cannot rely on type.IsValueType here - it's not set when the instruction operand is a typeref (as opposed to a typespec) + TypeDefinition typeDef = type.Resolve() as TypeDefinition; + return typeDef != null && typeDef.IsEnum; + } + + static bool? IsSigned(TypeReference type) + { + type = GetEnumUnderlyingType(type) ?? type; + if (type == null) + return null; + switch (type.MetadataType) { + case MetadataType.SByte: + case MetadataType.Int16: + case MetadataType.Int32: + case MetadataType.Int64: + case MetadataType.IntPtr: + return true; + case MetadataType.Byte: + case MetadataType.Char: + case MetadataType.UInt16: + case MetadataType.UInt32: + case MetadataType.UInt64: + case MetadataType.UIntPtr: + return false; + default: + return null; + } + } + + static bool OperandFitsInType(TypeReference type, int num) + { + type = GetEnumUnderlyingType(type) ?? type; + switch (type.MetadataType) { + case MetadataType.SByte: + return sbyte.MinValue <= num && num <= sbyte.MaxValue; + case MetadataType.Int16: + return short.MinValue <= num && num <= short.MaxValue; + case MetadataType.Byte: + return byte.MinValue <= num && num <= byte.MaxValue; + case MetadataType.Char: + return char.MinValue <= num && num <= char.MaxValue; + case MetadataType.UInt16: + return ushort.MinValue <= num && num <= ushort.MaxValue; + default: + return true; + } + } + + static bool IsArrayPointerOrReference(TypeReference type) + { + TypeSpecification typeSpec = type as TypeSpecification; + while (typeSpec != null) { + if (typeSpec is ArrayType || typeSpec is PointerType || typeSpec is ByReferenceType) + return true; + typeSpec = typeSpec.ElementType as TypeSpecification; + } + return false; + } + + internal static bool IsNullableType(TypeReference type) + { + return type != null && type.Name == "Nullable`1" && type.Namespace == "System"; + } + + public static TypeCode GetTypeCode(TypeReference type) + { + if (type == null) + return TypeCode.Empty; + switch (type.MetadataType) { + case MetadataType.Boolean: + return TypeCode.Boolean; + case MetadataType.Char: + return TypeCode.Char; + case MetadataType.SByte: + return TypeCode.SByte; + case MetadataType.Byte: + return TypeCode.Byte; + case MetadataType.Int16: + return TypeCode.Int16; + case MetadataType.UInt16: + return TypeCode.UInt16; + case MetadataType.Int32: + return TypeCode.Int32; + case MetadataType.UInt32: + return TypeCode.UInt32; + case MetadataType.Int64: + return TypeCode.Int64; + case MetadataType.UInt64: + return TypeCode.UInt64; + case MetadataType.Single: + return TypeCode.Single; + case MetadataType.Double: + return TypeCode.Double; + case MetadataType.String: + return TypeCode.String; + default: + return TypeCode.Object; + } + } + + /// <summary> + /// Clears the type inference data on the method. + /// </summary> + public static void Reset(ILBlock method) + { + foreach (ILExpression expr in method.GetSelfAndChildrenRecursive<ILExpression>()) { + expr.InferredType = null; + expr.ExpectedType = null; + ILVariable v = expr.Operand as ILVariable; + if (v != null && v.IsGenerated) + v.Type = null; + } + } + + public static bool IsSameType(TypeReference type1, TypeReference type2) + { + if (type1 == type2) + return true; + if (type1 == null || type2 == null) + return false; + return type1.FullName == type2.FullName; // TODO: implement this more efficiently? + } + } +} diff --git a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs new file mode 100644 index 00000000..7a9a09ed --- /dev/null +++ b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs @@ -0,0 +1,635 @@ +// 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 System.Linq; +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.ILAst +{ + internal class YieldReturnDecompiler + { + // For a description on the code generated by the C# compiler for yield return: + // http://csharpindepth.com/Articles/Chapter6/IteratorBlockImplementation.aspx + + // The idea here is: + // - Figure out whether the current method is instanciating an enumerator + // - 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. + + // 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; + MethodDefinition enumeratorCtor; + MethodDefinition disposeMethod; + FieldDefinition stateField; + FieldDefinition currentField; + Dictionary<FieldDefinition, ILVariable> fieldToParameterMap = new Dictionary<FieldDefinition, ILVariable>(); + List<ILNode> newBody; + + #region Run() method + public static void Run(DecompilerContext context, ILBlock method) + { + if (!context.Settings.YieldReturn) + return; // abort if enumerator decompilation is disabled + var yrd = new YieldReturnDecompiler(); + yrd.context = context; + if (!yrd.MatchEnumeratorCreationPattern(method)) + return; + yrd.enumeratorType = yrd.enumeratorCtor.DeclaringType; + #if DEBUG + if (Debugger.IsAttached) { + yrd.Run(); + } else { + #endif + try { + yrd.Run(); + } catch (SymbolicAnalysisFailedException) { + return; + } + #if DEBUG + } + #endif + method.Body.Clear(); + method.EntryGoto = null; + method.Body.AddRange(yrd.newBody); + + // 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(); + } + + void Run() + { + AnalyzeCtor(); + AnalyzeCurrentProperty(); + ResolveIEnumerableIEnumeratorFieldMapping(); + ConstructExceptionTable(); + AnalyzeMoveNext(); + TranslateFieldsToLocalAccess(); + } + #endregion + + #region Match the enumerator creation pattern + bool MatchEnumeratorCreationPattern(ILBlock method) + { + if (method.Body.Count == 0) + return false; + ILExpression newObj; + if (method.Body.Count == 1) { + // ret(newobj(...)) + if (method.Body[0].Match(ILCode.Ret, out newObj)) + return MatchEnumeratorCreationNewObj(newObj, out enumeratorCtor); + else + return false; + } + // stloc(var_1, newobj(..) + ILVariable var1; + if (!method.Body[0].Match(ILCode.Stloc, out var1, out newObj)) + return false; + if (!MatchEnumeratorCreationNewObj(newObj, out enumeratorCtor)) + return false; + + int i; + for (i = 1; i < method.Body.Count; i++) { + // stfld(..., ldloc(var_1), ldloc(parameter)) + FieldReference storedField; + ILExpression ldloc, loadParameter; + if (!method.Body[i].Match(ILCode.Stfld, out storedField, out ldloc, out loadParameter)) + break; + ILVariable loadedVar, loadedArg; + if (!ldloc.Match(ILCode.Ldloc, out loadedVar) || !loadParameter.Match(ILCode.Ldloc, out loadedArg)) + return false; + storedField = GetFieldDefinition(storedField); + if (loadedVar != var1 || storedField == null || !loadedArg.IsParameter) + return false; + fieldToParameterMap[(FieldDefinition)storedField] = loadedArg; + } + ILVariable var2; + ILExpression ldlocForStloc2; + if (i < method.Body.Count && method.Body[i].Match(ILCode.Stloc, out var2, out ldlocForStloc2)) { + // stloc(var_2, ldloc(var_1)) + if (ldlocForStloc2.Code != ILCode.Ldloc || ldlocForStloc2.Operand != var1) + return false; + i++; + } else { + // the compiler might skip the above instruction in release builds; in that case, it directly returns stloc.Operand + var2 = var1; + } + ILExpression retArg; + if (i < method.Body.Count && method.Body[i].Match(ILCode.Ret, out retArg)) { + // ret(ldloc(var_2)) + if (retArg.Code == ILCode.Ldloc && retArg.Operand == var2) { + return true; + } + } + return false; + } + + static FieldDefinition GetFieldDefinition(FieldReference field) + { + return CecilExtensions.ResolveWithinSameModule(field); + } + + static MethodDefinition GetMethodDefinition(MethodReference method) + { + return CecilExtensions.ResolveWithinSameModule(method); + } + + bool MatchEnumeratorCreationNewObj(ILExpression expr, out MethodDefinition ctor) + { + // newobj(CurrentType/...::.ctor, ldc.i4(-2)) + ctor = null; + if (expr.Code != ILCode.Newobj || expr.Arguments.Count != 1) + return false; + if (expr.Arguments[0].Code != ILCode.Ldc_I4) + return false; + int initialState = (int)expr.Arguments[0].Operand; + if (!(initialState == -2 || initialState == 0)) + return false; + ctor = GetMethodDefinition(expr.Operand as MethodReference); + if (ctor == null || ctor.DeclaringType.DeclaringType != context.CurrentType) + return false; + return IsCompilerGeneratorEnumerator(ctor.DeclaringType); + } + + public static bool IsCompilerGeneratorEnumerator(TypeDefinition type) + { + if (!(type.DeclaringType != null && type.IsCompilerGenerated())) + return false; + foreach (TypeReference i in type.Interfaces) { + if (i.Namespace == "System.Collections" && i.Name == "IEnumerator") + return true; + } + return false; + } + #endregion + + #region Figure out what the 'state' field is (analysis of .ctor()) + /// <summary> + /// Looks at the enumerator's ctor and figures out which of the fields holds the state. + /// </summary> + void AnalyzeCtor() + { + ILBlock method = CreateILAst(enumeratorCtor); + + foreach (ILNode node in method.Body) { + FieldReference field; + ILExpression instExpr; + ILExpression stExpr; + ILVariable arg; + if (node.Match(ILCode.Stfld, out field, out instExpr, out stExpr) && + instExpr.MatchThis() && + stExpr.Match(ILCode.Ldloc, out arg) && + arg.IsParameter && arg.OriginalParameter.Index == 0) + { + stateField = GetFieldDefinition(field); + } + } + if (stateField == 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; + } + #endregion + + #region Figure out what the 'current' field is (analysis of get_Current()) + /// <summary> + /// Looks at the enumerator's get_Current method and figures out which of the fields holds the current value. + /// </summary> + void AnalyzeCurrentProperty() + { + MethodDefinition getCurrentMethod = enumeratorType.Methods.FirstOrDefault( + m => m.Name.StartsWith("System.Collections.Generic.IEnumerator", StringComparison.Ordinal) + && m.Name.EndsWith(".get_Current", StringComparison.Ordinal)); + ILBlock method = CreateILAst(getCurrentMethod); + if (method.Body.Count == 1) { + // release builds directly return the current field + ILExpression retExpr; + FieldReference field; + ILExpression ldFromObj; + if (method.Body[0].Match(ILCode.Ret, out retExpr) && + retExpr.Match(ILCode.Ldfld, out field, out ldFromObj) && + ldFromObj.MatchThis()) + { + currentField = GetFieldDefinition(field); + } + } else if (method.Body.Count == 2) { + ILVariable v, v2; + ILExpression stExpr; + FieldReference field; + ILExpression ldFromObj; + ILExpression retExpr; + if (method.Body[0].Match(ILCode.Stloc, out v, out stExpr) && + stExpr.Match(ILCode.Ldfld, out field, out ldFromObj) && + ldFromObj.MatchThis() && + method.Body[1].Match(ILCode.Ret, out retExpr) && + retExpr.Match(ILCode.Ldloc, out v2) && + v == v2) + { + currentField = GetFieldDefinition(field); + } + } + if (currentField == null) + throw new SymbolicAnalysisFailedException(); + } + #endregion + + #region Figure out the mapping of IEnumerable fields to IEnumerator fields (analysis of GetEnumerator()) + void ResolveIEnumerableIEnumeratorFieldMapping() + { + MethodDefinition getEnumeratorMethod = enumeratorType.Methods.FirstOrDefault( + m => m.Name.StartsWith("System.Collections.Generic.IEnumerable", StringComparison.Ordinal) + && m.Name.EndsWith(".GetEnumerator", StringComparison.Ordinal)); + if (getEnumeratorMethod == null) + return; // no mappings (maybe it's just an IEnumerator implementation?) + + ILBlock method = CreateILAst(getEnumeratorMethod); + foreach (ILNode node in method.Body) { + FieldReference stField; + ILExpression stToObj; + ILExpression stExpr; + FieldReference ldField; + ILExpression ldFromObj; + if (node.Match(ILCode.Stfld, out stField, out stToObj, out stExpr) && + stExpr.Match(ILCode.Ldfld, out ldField, out ldFromObj) && + ldFromObj.MatchThis()) + { + FieldDefinition storedField = GetFieldDefinition(stField); + FieldDefinition loadedField = GetFieldDefinition(ldField); + if (storedField != null && loadedField != null) { + ILVariable mappedParameter; + if (fieldToParameterMap.TryGetValue(loadedField, out mappedParameter)) + fieldToParameterMap[storedField] = mappedParameter; + } + } + } + } + #endregion + + #region Construction of the exception table (analysis of Dispose()) + // We construct the exception table by analyzing the enumerator's Dispose() method. + + // Assumption: there are no loops/backward jumps + // We 'run' the code, with "state" being a symbolic variable + // so it can form expressions like "state + x" (when there's a sub instruction) + // For each instruction, we maintain a list of value ranges for state for which the instruction is reachable. + // This is (int.MinValue, int.MaxValue) for the first instruction. + // These ranges are propagated depending on the conditional jumps performed by the code. + + Dictionary<MethodDefinition, StateRange> finallyMethodToStateRange; + + void ConstructExceptionTable() + { + disposeMethod = enumeratorType.Methods.FirstOrDefault(m => m.Name == "System.IDisposable.Dispose"); + ILBlock ilMethod = CreateILAst(disposeMethod); + + var rangeAnalysis = new StateRangeAnalysis(ilMethod.Body[0], StateRangeAnalysisMode.IteratorDispose, stateField); + rangeAnalysis.AssignStateRanges(ilMethod.Body, ilMethod.Body.Count); + finallyMethodToStateRange = rangeAnalysis.finallyMethodToStateRange; + + // Now look at the finally blocks: + foreach (var tryFinally in ilMethod.GetSelfAndChildrenRecursive<ILTryCatchBlock>()) { + var range = rangeAnalysis.ranges[tryFinally.TryBlock.Body[0]]; + var finallyBody = tryFinally.FinallyBlock.Body; + if (finallyBody.Count != 2) + throw new SymbolicAnalysisFailedException(); + ILExpression call = finallyBody[0] as ILExpression; + if (call == null || call.Code != ILCode.Call || call.Arguments.Count != 1) + throw new SymbolicAnalysisFailedException(); + if (!call.Arguments[0].MatchThis()) + throw new SymbolicAnalysisFailedException(); + if (!finallyBody[1].Match(ILCode.Endfinally)) + throw new SymbolicAnalysisFailedException(); + + MethodDefinition mdef = GetMethodDefinition(call.Operand as MethodReference); + if (mdef == null || finallyMethodToStateRange.ContainsKey(mdef)) + throw new SymbolicAnalysisFailedException(); + finallyMethodToStateRange.Add(mdef, range); + } + rangeAnalysis = null; + } + #endregion + + #region Analysis of MoveNext() + ILVariable returnVariable; + ILLabel returnLabel; + ILLabel returnFalseLabel; + + void AnalyzeMoveNext() + { + MethodDefinition moveNextMethod = enumeratorType.Methods.FirstOrDefault(m => m.Name == "MoveNext"); + ILBlock ilMethod = CreateILAst(moveNextMethod); + + if (ilMethod.Body.Count == 0) + throw new SymbolicAnalysisFailedException(); + ILExpression lastReturnArg; + if (!ilMethod.Body.Last().Match(ILCode.Ret, out lastReturnArg)) + throw new SymbolicAnalysisFailedException(); + + // There are two possibilities: + if (lastReturnArg.Code == ILCode.Ldloc) { + // a) the compiler uses a variable for returns (in debug builds, or when there are try-finally blocks) + returnVariable = (ILVariable)lastReturnArg.Operand; + returnLabel = ilMethod.Body.ElementAtOrDefault(ilMethod.Body.Count - 2) as ILLabel; + if (returnLabel == null) + 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 SymbolicAnalysisFailedException(); + } + + ILTryCatchBlock tryFaultBlock = ilMethod.Body[0] as ILTryCatchBlock; + List<ILNode> body; + int bodyLength; + if (tryFaultBlock != null) { + // there are try-finally blocks + if (returnVariable == null) // in this case, we must use a return variable + throw new SymbolicAnalysisFailedException(); + // must be a try-fault block: + if (tryFaultBlock.CatchBlocks.Count != 0 || tryFaultBlock.FinallyBlock != null || tryFaultBlock.FaultBlock == null) + throw new SymbolicAnalysisFailedException(); + + ILBlock faultBlock = tryFaultBlock.FaultBlock; + // Ensure the fault block contains the call to Dispose(). + if (faultBlock.Body.Count != 2) + throw new SymbolicAnalysisFailedException(); + MethodReference disposeMethodRef; + ILExpression disposeArg; + if (!faultBlock.Body[0].Match(ILCode.Call, out disposeMethodRef, out disposeArg)) + throw new SymbolicAnalysisFailedException(); + if (GetMethodDefinition(disposeMethodRef) != disposeMethod || !disposeArg.MatchThis()) + throw new SymbolicAnalysisFailedException(); + if (!faultBlock.Body[1].Match(ILCode.Endfinally)) + throw new SymbolicAnalysisFailedException(); + + body = tryFaultBlock.TryBlock.Body; + bodyLength = body.Count; + } else { + // no try-finally blocks + body = ilMethod.Body; + if (returnVariable == null) + bodyLength = body.Count - 1; // all except for the return statement + else + bodyLength = body.Count - 2; // all except for the return label and statement + } + + // Now verify that the last instruction in the body is 'ret(false)' + if (returnVariable != null) { + // If we don't have a return variable, we already verified that above. + // If we do have one, check for 'stloc(returnVariable, ldc.i4(0))' + + // Maybe might be a jump to the return label after the stloc: + ILExpression leave = body.ElementAtOrDefault(bodyLength - 1) as ILExpression; + if (leave != null && (leave.Code == ILCode.Br || leave.Code == ILCode.Leave) && leave.Operand == returnLabel) + bodyLength--; + ILExpression store0 = body.ElementAtOrDefault(bodyLength - 1) as ILExpression; + if (store0 == null || store0.Code != ILCode.Stloc || store0.Operand != returnVariable) + throw new SymbolicAnalysisFailedException(); + if (store0.Arguments[0].Code != ILCode.Ldc_I4 || (int)store0.Arguments[0].Operand != 0) + throw new SymbolicAnalysisFailedException(); + + bodyLength--; // don't conside the stloc instruction to be part of the body + } + // The last element in the body usually is a label pointing to the 'ret(false)' + returnFalseLabel = body.ElementAtOrDefault(bodyLength - 1) as ILLabel; + // Note: in Roslyn-compiled code, returnFalseLabel may be null. + + 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 + + #region ConvertBody + struct SetState + { + public readonly int NewBodyPos; + public readonly int NewState; + + public SetState(int newBodyPos, int newState) + { + NewBodyPos = newBodyPos; + NewState = newState; + } + } + + void ConvertBody(List<ILNode> body, int startPos, int bodyLength, List<KeyValuePair<ILLabel, StateRange>> labels) + { + newBody = new List<ILNode>(); + newBody.Add(MakeGoTo(labels, 0)); + List<SetState> stateChanges = new List<SetState>(); + int currentState = -1; + // Copy all instructions from the old body to newBody. + for (int pos = startPos; pos < bodyLength; pos++) { + ILExpression expr = body[pos] as ILExpression; + if (expr != null && expr.Code == ILCode.Stfld && expr.Arguments[0].MatchThis()) { + // Handle stores to 'state' or 'current' + if (GetFieldDefinition(expr.Operand as FieldReference) == stateField) { + if (expr.Arguments[1].Code != ILCode.Ldc_I4) + throw new SymbolicAnalysisFailedException(); + currentState = (int)expr.Arguments[1].Operand; + stateChanges.Add(new SetState(newBody.Count, currentState)); + } else if (GetFieldDefinition(expr.Operand as FieldReference) == currentField) { + newBody.Add(new ILExpression(ILCode.YieldReturn, null, expr.Arguments[1])); + } else { + newBody.Add(body[pos]); + } + } else if (returnVariable != null && expr != null && expr.Code == ILCode.Stloc && expr.Operand == returnVariable) { + // 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 SymbolicAnalysisFailedException(); + int val = (int)expr.Arguments[0].Operand; + if (val == 0) { + newBody.Add(new ILExpression(ILCode.YieldBreak, null)); + } else if (val == 1) { + newBody.Add(MakeGoTo(labels, currentState)); + } else { + 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 SymbolicAnalysisFailedException(); + // handle direct return (e.g. in release builds) + int val = (int)expr.Arguments[0].Operand; + if (val == 0) { + newBody.Add(new ILExpression(ILCode.YieldBreak, null)); + } else if (val == 1) { + newBody.Add(MakeGoTo(labels, currentState)); + } else { + 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 SymbolicAnalysisFailedException(); + StateRange stateRange; + 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 SymbolicAnalysisFailedException(); + newBody.Add(new ILExpression(ILCode.YieldBreak, null)); + } else if (finallyMethodToStateRange.TryGetValue(method, out stateRange)) { + // Call to Finally-method + int index = stateChanges.FindIndex(ss => stateRange.Contains(ss.NewState)); + if (index < 0) + throw new SymbolicAnalysisFailedException(); + + ILLabel label = new ILLabel(); + label.Name = "JumpOutOfTryFinally" + stateChanges[index].NewState; + newBody.Add(new ILExpression(ILCode.Leave, label)); + + SetState stateChange = stateChanges[index]; + // Move all instructions from stateChange.Pos to newBody.Count into a try-block + stateChanges.RemoveRange(index, stateChanges.Count - index); // remove all state changes up to the one we found + ILTryCatchBlock tryFinally = new ILTryCatchBlock(); + tryFinally.TryBlock = new ILBlock(newBody.GetRange(stateChange.NewBodyPos, newBody.Count - stateChange.NewBodyPos)); + newBody.RemoveRange(stateChange.NewBodyPos, newBody.Count - stateChange.NewBodyPos); // remove all nodes that we just moved into the try block + tryFinally.CatchBlocks = new List<ILTryCatchBlock.CatchBlock>(); + tryFinally.FinallyBlock = ConvertFinallyBlock(method); + newBody.Add(tryFinally); + newBody.Add(label); + } + } else { + newBody.Add(body[pos]); + } + } + newBody.Add(new ILExpression(ILCode.YieldBreak, null)); + } + + ILExpression MakeGoTo(ILLabel targetLabel) + { + Debug.Assert(targetLabel != null); + if (targetLabel == returnFalseLabel) + return new ILExpression(ILCode.YieldBreak, null); + else + return new ILExpression(ILCode.Br, targetLabel); + } + + ILExpression MakeGoTo(List<KeyValuePair<ILLabel, StateRange>> labels, int state) + { + foreach (var pair in labels) { + if (pair.Value.Contains(state)) + return MakeGoTo(pair.Key); + } + throw new SymbolicAnalysisFailedException(); + } + + ILBlock ConvertFinallyBlock(MethodDefinition finallyMethod) + { + ILBlock block = CreateILAst(finallyMethod); + // Get rid of assignment to state + FieldReference stfld; + List<ILExpression> args; + if (block.Body.Count > 0 && block.Body[0].Match(ILCode.Stfld, out stfld, out args)) { + if (GetFieldDefinition(stfld) == stateField && args[0].MatchThis()) + block.Body.RemoveAt(0); + } + // Convert ret to endfinally + foreach (ILExpression expr in block.GetSelfAndChildrenRecursive<ILExpression>()) { + if (expr.Code == ILCode.Ret) + expr.Code = ILCode.Endfinally; + } + return block; + } + #endregion + + #region TranslateFieldsToLocalAccess + void TranslateFieldsToLocalAccess() + { + TranslateFieldsToLocalAccess(newBody, fieldToParameterMap); + } + + internal static void TranslateFieldsToLocalAccess(List<ILNode> newBody, Dictionary<FieldDefinition, ILVariable> fieldToParameterMap) + { + var fieldToLocalMap = new DefaultDictionary<FieldDefinition, ILVariable>(f => new ILVariable { Name = f.Name, Type = f.FieldType }); + foreach (ILNode node in newBody) { + foreach (ILExpression expr in node.GetSelfAndChildrenRecursive<ILExpression>()) { + FieldDefinition field = GetFieldDefinition(expr.Operand as FieldReference); + if (field != null) { + switch (expr.Code) { + case ILCode.Ldfld: + if (expr.Arguments[0].MatchThis()) { + expr.Code = ILCode.Ldloc; + if (fieldToParameterMap.ContainsKey(field)) { + expr.Operand = fieldToParameterMap[field]; + } else { + expr.Operand = fieldToLocalMap[field]; + } + expr.Arguments.Clear(); + } + break; + case ILCode.Stfld: + if (expr.Arguments[0].MatchThis()) { + expr.Code = ILCode.Stloc; + if (fieldToParameterMap.ContainsKey(field)) { + expr.Operand = fieldToParameterMap[field]; + } else { + expr.Operand = fieldToLocalMap[field]; + } + expr.Arguments.RemoveAt(0); + } + break; + case ILCode.Ldflda: + if (expr.Arguments[0].MatchThis()) { + expr.Code = ILCode.Ldloca; + if (fieldToParameterMap.ContainsKey(field)) { + expr.Operand = fieldToParameterMap[field]; + } else { + expr.Operand = fieldToLocalMap[field]; + } + expr.Arguments.Clear(); + } + break; + } + } + } + } + } + #endregion + } +} |