From ec610a442263f01ea75ef2ad1974bf590b67bc35 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 16 Sep 2017 18:26:35 +0200 Subject: [PATCH] Clean up mono yield-return decompilation. --- .../IL/ControlFlow/AsyncAwaitDecompiler.cs | 2 +- .../IL/ControlFlow/StateRangeAnalysis.cs | 2 + .../IL/ControlFlow/YieldReturnDecompiler.cs | 54 ++++++++++++++++--- 3 files changed, 51 insertions(+), 7 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs index 051e6fe9a..5f367ccce 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs @@ -863,7 +863,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow context.StepEndGroup(keepIfEmpty: true); } - Block GetBodyEntryPoint(BlockContainer body) + internal static Block GetBodyEntryPoint(BlockContainer body) { if (body == null) return null; diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs b/ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs index 475dcc872..fa4fa097c 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs @@ -74,6 +74,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow evalContext.AddStateVariable(cachedStateVar); } + public IEnumerable CachedStateVars { get => evalContext.StateVariables; } + /// /// Creates a new StateRangeAnalysis with the same settings, including any cached state vars /// discovered by this analysis. diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs index c391af8c5..5fc41d39a 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs @@ -17,7 +17,6 @@ // DEALINGS IN THE SOFTWARE. using ICSharpCode.Decompiler.CSharp; -using ICSharpCode.Decompiler.FlowAnalysis; using ICSharpCode.Decompiler.IL.Transforms; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.Util; @@ -26,8 +25,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace ICSharpCode.Decompiler.IL.ControlFlow { @@ -97,6 +94,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow /// ILVariable skipFinallyBodies; + /// + /// Set of variables might hold copies of the generated state field. + /// + HashSet cachedStateVars; + #region Run() method public void Run(ILFunction function, ILTransformContext context) { @@ -113,6 +115,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow this.decompiledFinallyMethods.Clear(); this.returnStores.Clear(); this.skipFinallyBodies = null; + this.cachedStateVars = null; if (!MatchEnumeratorCreationPattern(function)) return; BlockContainer newBody; @@ -159,9 +162,16 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow context.Step("Translate fields to local accesses", function); TranslateFieldsToLocalAccess(function, function, fieldToParameterMap); - // On mono, we still need to remove traces of the state variable: - if (isCompiledWithMono && fieldToParameterMap.TryGetValue(stateField, out var stateVar)) { - returnStores.AddRange(stateVar.StoreInstructions.OfType()); + CleanSkipFinallyBodies(function); + + // On mono, we still need to remove traces of the state variable(s): + if (isCompiledWithMono) { + if (fieldToParameterMap.TryGetValue(stateField, out var stateVar)) { + returnStores.AddRange(stateVar.StoreInstructions.OfType()); + } + foreach (var cachedStateVar in cachedStateVars) { + returnStores.AddRange(cachedStateVar.StoreInstructions.OfType()); + } } if (returnStores.Count > 0) { @@ -548,6 +558,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow rangeAnalysis.skipFinallyBodies = skipFinallyBodies; rangeAnalysis.CancellationToken = context.CancellationToken; rangeAnalysis.AssignStateRanges(body, LongSet.Universe); + cachedStateVars = rangeAnalysis.CachedStateVars.ToHashSet(); var newBody = ConvertBody(body, rangeAnalysis); moveNextFunction.Variables.Clear(); @@ -964,5 +975,36 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow return null; } #endregion + + + /// + /// Eliminates usage of doFinallyBodies + /// + private void CleanSkipFinallyBodies(ILFunction function) + { + if (skipFinallyBodies == null) { + return; // only mono-compiled code uses skipFinallyBodies + } + context.StepStartGroup("CleanSkipFinallyBodies", function); + Block entryPoint = AsyncAwaitDecompiler.GetBodyEntryPoint(function.Body as BlockContainer); + if (skipFinallyBodies.StoreInstructions.Count != 0 || skipFinallyBodies.AddressCount != 0) { + // misdetected another variable as doFinallyBodies? + // Fortunately removing the initial store of 0 is harmless, as we + // default-initialize the variable on uninit uses + return; + } + foreach (var tryFinally in function.Descendants.OfType()) { + entryPoint = AsyncAwaitDecompiler.GetBodyEntryPoint(tryFinally.FinallyBlock as BlockContainer); + if (entryPoint?.Instructions[0] is IfInstruction ifInst) { + if (ifInst.Condition is LogicNot logicNot && logicNot.Argument.MatchLdLoc(skipFinallyBodies)) { + context.Step("Remove if (skipFinallyBodies) from try-finally", tryFinally); + // condition will always be true now that we're using 'yield' instructions + entryPoint.Instructions[0] = ifInst.TrueInst; + entryPoint.Instructions.RemoveRange(1, entryPoint.Instructions.Count - 1); + } + } + } + context.StepEndGroup(keepIfEmpty: true); + } } }