diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index d4cbbebbb..b6bb3bf99 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -414,11 +414,12 @@ namespace ICSharpCode.Decompiler.CSharp // If there's an extra leave inside the block, extract it into optionalReturnAfterLoop. var loopContainer = UnwrapNestedContainerIfPossible(container, out var optionalReturnAfterLoop); // Detect whether we're dealing with a while loop with multiple embedded statements. - var loop = DetectedLoop.DetectLoop(loopContainer); - if (loop.Kind != LoopKind.While || !(loop.Body is Block body)) + if (loopContainer.Kind != ContainerKind.While) + return null; + if (!loopContainer.MatchConditionBlock(loopContainer.EntryPoint, out var conditionInst, out var body)) return null; // The loop condition must be a call to enumerator.MoveNext() - var condition = exprBuilder.TranslateCondition(loop.Conditions.Single()); + var condition = exprBuilder.TranslateCondition(conditionInst); var m2 = moveNextConditionPattern.Match(condition.Expression); if (!m2.Success) return null; @@ -427,8 +428,7 @@ namespace ICSharpCode.Decompiler.CSharp if (enumeratorVar2 != enumeratorVar) return null; // Detect which foreach-variable transformation is necessary/possible. - var transformation = DetectGetCurrentTransformation(container, body, enumeratorVar, condition.ILInstructions.Single(), - out var singleGetter, out var foreachVariable); + var transformation = DetectGetCurrentTransformation(container, body, enumeratorVar, conditionInst, out var singleGetter, out var foreachVariable); if (transformation == RequiredGetCurrentTransformation.NoForeach) return null; // The existing foreach variable, if found, can only be used in the loop container. @@ -475,7 +475,7 @@ namespace ICSharpCode.Decompiler.CSharp // Convert the modified body to C# AST: var whileLoop = (WhileStatement)ConvertAsBlock(container).First(); BlockStatement foreachBody = (BlockStatement)whileLoop.EmbeddedStatement.Detach(); - + // Remove the first statement, as it is the foreachVariable = enumerator.Current; statement. Statement firstStatement = foreachBody.Statements.First(); if (firstStatement is LabelStatement) { @@ -494,7 +494,7 @@ namespace ICSharpCode.Decompiler.CSharp }; // Add the variable annotation for highlighting (TokenTextWriter expects it directly on the ForeachStatement). foreachStmt.AddAnnotation(new ILVariableResolveResult(foreachVariable, foreachVariable.Type)); - foreachStmt.AddAnnotation(new ForeachAnnotation(inst.ResourceExpression, loop.Conditions.Single(), singleGetter)); + foreachStmt.AddAnnotation(new ForeachAnnotation(inst.ResourceExpression, conditionInst, singleGetter)); // If there was an optional return statement, return it as well. if (optionalReturnAfterLoop != null) { return new BlockStatement { diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 272c9c294..bb98e3b53 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -300,7 +300,6 @@ - diff --git a/ICSharpCode.Decompiler/IL/DetectedLoop.cs b/ICSharpCode.Decompiler/IL/DetectedLoop.cs deleted file mode 100644 index 4d234be29..000000000 --- a/ICSharpCode.Decompiler/IL/DetectedLoop.cs +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (c) 2017 Siegfried Pammer -// -// 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.Util; - -namespace ICSharpCode.Decompiler.IL -{ - public enum LoopKind { None, While, DoWhile, For } - - public class DetectedLoop - { - public BlockContainer Container { get; } - public LoopKind Kind { get; private set; } - public ILInstruction[] Conditions { get; private set; } - public Block IncrementBlock { get; private set; } - public Block ContinueJumpTarget { get; private set; } // jumps to this block are "continue;" jumps - public ILInstruction Body { get; private set; } // null in case of DoWhile - public Block[] AdditionalBlocks { get; private set; } // blocks to be merged into the loop body - public ILVariable IncrementTarget { get; private set; } // null, except in case of For - - private DetectedLoop(BlockContainer container) - { - this.Container = container; - } - - /// - /// Gets whether the statement is 'simple' (usable as for loop iterator): - /// Currently we only accept calls and assignments. - /// - private static bool IsSimpleStatement(ILInstruction inst) - { - switch (inst.OpCode) { - case OpCode.Call: - case OpCode.CallVirt: - case OpCode.NewObj: - case OpCode.StLoc: - case OpCode.StObj: - case OpCode.CompoundAssignmentInstruction: - return true; - default: - return false; - } - } - - public static DetectedLoop DetectLoop(BlockContainer container) - { - return new DetectedLoop(container).DetectLoopInternal(); - } - - private static Block FindDoWhileConditionBlock(BlockContainer container, List conditions) - { - foreach (var b in container.Blocks) { - if (b.Instructions.Last().MatchBranch(container.EntryPoint)) { - // potentially the do-while-condition block - int i = b.Instructions.Count - 2; - while (i >= 0 && b.Instructions[i] is IfInstruction ifInst - && ifInst.TrueInst.MatchLeave(container) && ifInst.FalseInst.MatchNop()) { - conditions.Add(ifInst.Condition); - i--; - } - if (i == -1 && conditions.Any()) { - return b; - } - } - } - return null; - } - - private DetectedLoop DetectLoopInternal() - { - if (Container.EntryPoint.IncomingEdgeCount <= 1) { - Kind = LoopKind.None; - return this; - } - Kind = LoopKind.While; - ContinueJumpTarget = Container.EntryPoint; - if (Container.EntryPoint.Instructions.Count == 2 - && Container.EntryPoint.Instructions[0].MatchIfInstruction(out var conditionInst, out var trueInst) - && Container.EntryPoint.Instructions[1].MatchLeave(Container)) { - // detected while(condition)-loop or for-loop - // we have to check if there's an increment block before converting the loop body using ConvertAsBlock(trueInst) - // and set the continueJumpTarget to correctly convert 'br incrementBlock' instructions to continue; - IncrementBlock = null; - if (Container.EntryPoint.IncomingEdgeCount == 2) { - IncrementBlock = Container.Blocks.SingleOrDefault( - b => b.Instructions.Last().MatchBranch(Container.EntryPoint) - && b.Instructions.SkipLast(1).All(IsSimpleStatement)); - if (IncrementBlock != null) - ContinueJumpTarget = IncrementBlock; - } - Conditions = new[] { conditionInst }; - Body = trueInst; - if (IncrementBlock != null) { - // for-loop - Kind = LoopKind.For; - if (IncrementBlock.Instructions[0] is StLoc increment) - IncrementTarget = increment.Variable; - AdditionalBlocks = Container.Blocks.Skip(1).Where(b => b != IncrementBlock).ToArray(); - return this; - } else if (trueInst is Block block) { - var last = block.Instructions.LastOrDefault(); - var secondToLast = block.Instructions.SecondToLastOrDefault(); - if (last != null && secondToLast != null && last.MatchBranch(Container.EntryPoint) && - MatchIncrement(secondToLast, out var variable) && conditionInst.Children.Any(c => c.MatchLdLoc(variable))) { - Kind = LoopKind.For; - IncrementTarget = variable; - AdditionalBlocks = Container.Blocks.Skip(1).ToArray(); - return this; - } - } - AdditionalBlocks = Container.Blocks.Skip(1).ToArray(); - } else { - // do-while or while(true)-loop - if (Container.EntryPoint.IncomingEdgeCount == 2) { - var conditions = new List(); - Block conditionBlock = FindDoWhileConditionBlock(Container, conditions); - if (conditionBlock != null) { - Kind = LoopKind.DoWhile; - ContinueJumpTarget = conditionBlock; - Body = null; - Conditions = conditions.ToArray(); - AdditionalBlocks = Container.Blocks.Where(b => b != conditionBlock).ToArray(); - } - } - } - return this; - } - - static bool MatchIncrement(ILInstruction inst, out ILVariable variable) - { - if (!inst.MatchStLoc(out variable, out var value)) - return false; - if (!value.MatchBinaryNumericInstruction(BinaryNumericOperator.Add, out var left, out var right)) { - if (value is CompoundAssignmentInstruction cai) { - left = cai.Target; - } else return false; - } - return left.MatchLdLoc(variable); - } - } -} diff --git a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs index e7967e44e..bfe03f90f 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs @@ -156,10 +156,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms var loopCounters = new HashSet(); foreach (BlockContainer possibleLoop in function.Descendants.OfType()) { - if (possibleLoop.EntryPoint.IncomingEdgeCount == 1) continue; - var loop = DetectedLoop.DetectLoop(possibleLoop); - if (loop.Kind != LoopKind.For || loop.IncrementTarget == null) continue; - loopCounters.Add(loop.IncrementTarget); + if (possibleLoop.Kind != ContainerKind.For) continue; + foreach (var inst in possibleLoop.Blocks.Last().Instructions) { + if (HighLevelLoopTransform.MatchIncrement(inst, out var variable)) + loopCounters.Add(variable); + } } return loopCounters; diff --git a/ICSharpCode.Decompiler/IL/Transforms/HighLevelLoopTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/HighLevelLoopTransform.cs index 26ec3c5f8..0931887b4 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/HighLevelLoopTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/HighLevelLoopTransform.cs @@ -167,7 +167,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return true; } - static bool MatchIncrement(ILInstruction inst, out ILVariable variable) + public static bool MatchIncrement(ILInstruction inst, out ILVariable variable) { if (!inst.MatchStLoc(out variable, out var value)) return false;