diff --git a/Debugger/ILSpy.Debugger/AvalonEdit/TextMarkerService.cs b/Debugger/ILSpy.Debugger/AvalonEdit/TextMarkerService.cs index 7d956ab89..0d7536206 100644 --- a/Debugger/ILSpy.Debugger/AvalonEdit/TextMarkerService.cs +++ b/Debugger/ILSpy.Debugger/AvalonEdit/TextMarkerService.cs @@ -126,7 +126,7 @@ namespace ILSpy.Debugger.AvalonEdit return; int lineStart = line.Offset; int lineEnd = lineStart + line.Length; - foreach (TextMarker marker in markers.FindOverlappingSegments(lineStart, line.Length)) { + foreach (TextMarker marker in markers.FindOverlappingSegments(lineStart, line.Length).Reverse()) { Brush foregroundBrush = null; if (marker.ForegroundColor != null) { foregroundBrush = new SolidColorBrush(marker.ForegroundColor.Value); @@ -166,7 +166,7 @@ namespace ILSpy.Debugger.AvalonEdit return; int viewStart = visualLines.First().FirstDocumentLine.Offset; int viewEnd = visualLines.Last().LastDocumentLine.Offset + visualLines.Last().LastDocumentLine.Length; - foreach (TextMarker marker in markers.FindOverlappingSegments(viewStart, viewEnd - viewStart)) { + foreach (TextMarker marker in markers.FindOverlappingSegments(viewStart, viewEnd - viewStart).Reverse()) { if (marker.BackgroundColor != null) { BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder(); geoBuilder.AlignToWholePixels = true; diff --git a/Debugger/ILSpy.Debugger/Bookmarks/CurrentLineBookmark.cs b/Debugger/ILSpy.Debugger/Bookmarks/CurrentLineBookmark.cs index 47caa0418..3c59d1d21 100644 --- a/Debugger/ILSpy.Debugger/Bookmarks/CurrentLineBookmark.cs +++ b/Debugger/ILSpy.Debugger/Bookmarks/CurrentLineBookmark.cs @@ -92,7 +92,7 @@ namespace ILSpy.Debugger.Bookmarks public override ITextMarker CreateMarker(ITextMarkerService markerService, int offset, int length) { - ITextMarker marker = markerService.Create(offset + startColumn - 1, Math.Max(endColumn - startColumn, 1)); + ITextMarker marker = markerService.Create(offset + startColumn - 1, length); marker.BackgroundColor = Colors.Yellow; marker.ForegroundColor = Colors.Blue; return marker; diff --git a/Debugger/ILSpy.Debugger/Bookmarks/MarkerBookmark.cs b/Debugger/ILSpy.Debugger/Bookmarks/MarkerBookmark.cs index aebc3d461..194aac4e4 100644 --- a/Debugger/ILSpy.Debugger/Bookmarks/MarkerBookmark.cs +++ b/Debugger/ILSpy.Debugger/Bookmarks/MarkerBookmark.cs @@ -22,9 +22,6 @@ using ILSpy.Debugger.AvalonEdit; namespace ILSpy.Debugger.Bookmarks { - /// - /// Description of MarkerBookmark. - /// public abstract class MarkerBookmark : BookmarkBase { public MarkerBookmark(string typeName, AstLocation location) : base(typeName, location) diff --git a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs index f54007484..7e4e23e3d 100644 --- a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs +++ b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs @@ -110,21 +110,18 @@ namespace Decompiler } } else if (node is ILWhileLoop) { ILWhileLoop ilLoop = (ILWhileLoop)node; - if (ilLoop.PreLoopLabel != null) - yield return TransformNode(ilLoop.PreLoopLabel).Single(); WhileStatement whileStmt = new WhileStatement() { Condition = ilLoop.Condition != null ? MakeBranchCondition(ilLoop.Condition) : new PrimitiveExpression(true), EmbeddedStatement = TransformBlock(ilLoop.BodyBlock) }; yield return whileStmt; - if (ilLoop.PostLoopGoto != null) - yield return (Statement)TransformExpression(ilLoop.PostLoopGoto); } else if (node is ILCondition) { ILCondition conditionalNode = (ILCondition)node; + bool hasFalseBlock = conditionalNode.FalseBlock.EntryGoto != null || conditionalNode.FalseBlock.Body.Count > 0; yield return new Ast.IfElseStatement { Condition = MakeBranchCondition(conditionalNode.Condition), TrueStatement = TransformBlock(conditionalNode.TrueBlock), - FalseStatement = TransformBlock(conditionalNode.FalseBlock) + FalseStatement = hasFalseBlock ? TransformBlock(conditionalNode.FalseBlock) : null }; } else if (node is ILSwitch) { ILSwitch ilSwitch = (ILSwitch)node; @@ -296,6 +293,10 @@ namespace Decompiler TrueExpression = (Expression)TransformExpression(byteCode.Arguments[1]), FalseExpression = (Expression)TransformExpression(byteCode.Arguments[2]), }; + case ILCode.LoopBreak: + return new Ast.BreakStatement(); + case ILCode.LoopContinue: + return new Ast.ContinueStatement(); } List args = TransformExpressionArguments(byteCode); @@ -573,7 +574,7 @@ namespace Decompiler } else { return InlineAssembly(byteCode, args); } - case Code.Leave: return null; + case Code.Leave: return new GotoStatement() { Label = ((ILLabel)operand).Name }; case Code.Localloc: return InlineAssembly(byteCode, args); case Code.Mkrefany: return InlineAssembly(byteCode, args); case Code.Newobj: diff --git a/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs b/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs index 15dc59905..78a9d3596 100644 --- a/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs +++ b/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs @@ -204,8 +204,7 @@ namespace Decompiler.Transforms Left = new Backreference("ident").ToExpression(), Operator = AssignmentOperatorType.Any, Right = new AnyNode().ToExpression() - })).ToStatement(), - new ContinueStatement() + })).ToStatement() } }; diff --git a/ICSharpCode.Decompiler/Ast/Transforms/RemoveDeadLabels.cs b/ICSharpCode.Decompiler/Ast/Transforms/RemoveDeadLabels.cs deleted file mode 100644 index b8f65771a..000000000 --- a/ICSharpCode.Decompiler/Ast/Transforms/RemoveDeadLabels.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Collections.Generic; -using ICSharpCode.NRefactory.CSharp; - -namespace Decompiler.Transforms.Ast -{ - public class RemoveDeadLabels : DepthFirstAstVisitor - { - List usedLabels = new List(); - bool collectingUsedLabels; - - public override object VisitConstructorDeclaration(ConstructorDeclaration constructorDeclaration, object data) - { - collectingUsedLabels = true; - base.VisitConstructorDeclaration(constructorDeclaration, data); - collectingUsedLabels = false; - base.VisitConstructorDeclaration(constructorDeclaration, data); - return null; - } - - public override object VisitMethodDeclaration(MethodDeclaration methodDeclaration, object data) - { - collectingUsedLabels = true; - base.VisitMethodDeclaration(methodDeclaration, data); - collectingUsedLabels = false; - base.VisitMethodDeclaration(methodDeclaration, data); - return null; - } - - public override object VisitAccessor(Accessor accessor, object data) - { - collectingUsedLabels = true; - base.VisitAccessor(accessor, data); - collectingUsedLabels = false; - return base.VisitAccessor(accessor, data); - } - - public override object VisitGotoStatement(GotoStatement gotoStatement, object data) - { - if (collectingUsedLabels) { - usedLabels.Add(gotoStatement.Label); - } - return null; - } - - public override object VisitLabelStatement(LabelStatement labelStatement, object data) - { - if (!collectingUsedLabels) { - if (!usedLabels.Contains(labelStatement.Label)) { - labelStatement.Remove(); - } - } - return null; - } - } -} diff --git a/ICSharpCode.Decompiler/Ast/Transforms/RemoveEmptyElseBody.cs b/ICSharpCode.Decompiler/Ast/Transforms/RemoveEmptyElseBody.cs deleted file mode 100644 index a33065232..000000000 --- a/ICSharpCode.Decompiler/Ast/Transforms/RemoveEmptyElseBody.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Linq; -using ICSharpCode.NRefactory.CSharp; - -namespace Decompiler.Transforms.Ast -{ - public class RemoveEmptyElseBody: DepthFirstAstVisitor - { - public override object VisitIfElseStatement(IfElseStatement ifElseStatement, object data) - { - base.VisitIfElseStatement(ifElseStatement, data); - BlockStatement block = ifElseStatement.FalseStatement as BlockStatement; - if (block != null && !block.Statements.Any()) { - ifElseStatement.FalseStatement = null; - } - return null; - } - } -} diff --git a/ICSharpCode.Decompiler/Ast/Transforms/RemoveGotos.cs b/ICSharpCode.Decompiler/Ast/Transforms/RemoveGotos.cs deleted file mode 100644 index 69a935568..000000000 --- a/ICSharpCode.Decompiler/Ast/Transforms/RemoveGotos.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using ICSharpCode.NRefactory.CSharp; - -namespace Decompiler.Transforms.Ast -{ - public class RemoveGotos: DepthFirstAstVisitor - { - Stack enteredLoops = new Stack(); - - Statement CurrentLoop { - get { - if (enteredLoops.Count > 0) { - return enteredLoops.Peek(); - } else { - return null; - } - } - } - - Statement CurrentLoopBody { - get { - if (this.CurrentLoop == null) { - return null; - } else if (this.CurrentLoop is ForStatement) { - return ((ForStatement)this.CurrentLoop).EmbeddedStatement; - } else if (this.CurrentLoop is WhileStatement) { - return ((WhileStatement)this.CurrentLoop).EmbeddedStatement; - } else { - return null; - } - } - } - - public override object VisitForStatement(ForStatement forStatement, object data) - { - enteredLoops.Push(forStatement); - base.VisitForStatement(forStatement, data); - enteredLoops.Pop(); - return null; - } - - public override object VisitWhileStatement(WhileStatement whileStatement, object data) - { - enteredLoops.Push(whileStatement); - base.VisitWhileStatement(whileStatement, data); - enteredLoops.Pop(); - return null; - } - - public override object VisitBlockStatement(BlockStatement blockStatement, object data) - { - base.VisitBlockStatement(blockStatement, data); - - // Remove redundant jump at the end of block - AstNode lastStmt = blockStatement.Children.LastOrDefault(); - // End of for loop - if (lastStmt is ContinueStatement && - blockStatement.Parent is ForStatement) - { - lastStmt.Remove(); - return null; - } - // End of method - if (lastStmt is ReturnStatement && - (blockStatement.Parent is MethodDeclaration || blockStatement.Parent is ConstructorDeclaration) && - ((ReturnStatement)lastStmt).Expression.IsNull) - { - lastStmt.Remove(); - return null; - } - - return null; - } - - // Get the next statement that will be executed after this one - // May return null - public static AstNode GetNextStatement(Statement statement) - { - if (statement == null) throw new ArgumentNullException(); - - Statement next = (Statement)statement.NextSibling; - - if (next != null) { - return EnterBlockStatement(next); - } else { - if (statement.Parent is BlockStatement && - statement.Parent.Parent is Statement) { - return ExitBlockStatement((Statement)statement.Parent.Parent); - } else { - return null; - } - } - } - - // Get the statement that will be executed once the given block exits by the end brace - // May return null - public static AstNode ExitBlockStatement(Statement statement) - { - if (statement == null) throw new ArgumentNullException(); - - // When an 'if' body is finished the execution continues with the - // next statement after the 'if' statement - if (statement is IfElseStatement) { - return GetNextStatement((IfElseStatement)statement); - } - - // When a 'for' body is finished the execution continues by: - // Iterator; Condition; Body - if (statement is ForStatement) { - ForStatement forLoop = statement as ForStatement; - if (forLoop.Iterators.Any()) { - return forLoop.Iterators.First(); - } else if (!forLoop.Condition.IsNull) { - return forLoop.Condition; - } else { - return EnterBlockStatement((Statement)forLoop.EmbeddedStatement.FirstChild); - } - } - - return null; - } - - // Get the first statement that will be executed in the given block - public static AstNode EnterBlockStatement(Statement statement) - { - if (statement == null) throw new ArgumentNullException(); - - // For loop starts as follows: Initializers; Condition; Body - if (statement is ForStatement) { - ForStatement forLoop = statement as ForStatement; - if (forLoop.Initializers.Any()) { - return forLoop.Initializers.First(); - } else if (!forLoop.Condition.IsNull) { - return forLoop.Condition; - } else if (forLoop.EmbeddedStatement.Children.FirstOrDefault() is Statement) { - return EnterBlockStatement((Statement)forLoop.EmbeddedStatement.FirstChild); // Simplify again - } - } - - return statement; // Can not simplify - } - - public override object VisitGotoStatement(GotoStatement gotoStatement, object data) - { - // Remove redundant goto which goes to a label that imideately follows - AstNode fallthoughTarget = GetNextStatement(gotoStatement); - while(true) { - if (fallthoughTarget is LabelStatement) { - if ((fallthoughTarget as LabelStatement).Label == gotoStatement.Label) { - gotoStatement.Remove(); - return null; - } else { - fallthoughTarget = GetNextStatement((LabelStatement)fallthoughTarget); - continue; - } - } - break; - } - - // Replace goto with 'break' - // Break statement moves right outside the looop - if (CurrentLoop != null) { - AstNode breakTarget = GetNextStatement(CurrentLoop); - if ((breakTarget is LabelStatement) && - (breakTarget as LabelStatement).Label == gotoStatement.Label) { - gotoStatement.ReplaceWith(new BreakStatement()); - return null; - } - } - - // Replace goto with 'continue' - // Continue statement which moves at the very end of loop - if (CurrentLoop != null && - (CurrentLoopBody is BlockStatement) && - ((CurrentLoopBody as BlockStatement).LastChild as LabelStatement) != null && - ((CurrentLoopBody as BlockStatement).LastChild as LabelStatement).Label == gotoStatement.Label) { - gotoStatement.ReplaceWith(new ContinueStatement()); - return null; - } - - // Replace goto with 'continue' - // Jump before while - if (CurrentLoop is WhileStatement && - CurrentLoop.PrevSibling != null && - CurrentLoop.PrevSibling is LabelStatement && - (CurrentLoop.PrevSibling as LabelStatement).Label == gotoStatement.Label) { - gotoStatement.ReplaceWith(new ContinueStatement()); - return null; - } - - // Replace goto with 'continue' - // Continue statement which moves at the very start of for loop - if (CurrentLoop != null) { - AstNode continueTarget = ExitBlockStatement(CurrentLoop); // The start of the loop - if ((continueTarget is LabelStatement) && - (continueTarget as LabelStatement).Label == gotoStatement.Label) { - gotoStatement.ReplaceWith(new ContinueStatement()); - return null; - } - } - - return null; - } - } -} diff --git a/ICSharpCode.Decompiler/Ast/Transforms/RestoreLoop.cs b/ICSharpCode.Decompiler/Ast/Transforms/RestoreLoop.cs deleted file mode 100644 index d177fc923..000000000 --- a/ICSharpCode.Decompiler/Ast/Transforms/RestoreLoop.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Linq; -using ICSharpCode.NRefactory.CSharp; - -namespace Decompiler.Transforms.Ast -{ - public class RestoreLoop: DepthFirstAstVisitor - { - public override object VisitForStatement(ForStatement forStatement, object data) - { - base.VisitForStatement(forStatement, data); - - // Restore loop initializer - if (!forStatement.Initializers.Any()) { - VariableDeclarationStatement varDeclr = forStatement.PrevSibling as VariableDeclarationStatement; - if (varDeclr != null) { - varDeclr.ReplaceWith(Statement.Null); - forStatement.Initializers.Add(varDeclr); - } - } - - // Restore loop condition - if (forStatement.Condition.IsNull && - forStatement.EmbeddedStatement.Children.Count() >= 3) - { - IfElseStatement condition = forStatement.EmbeddedStatement.Children.First() as IfElseStatement; - BreakStatement breakStmt = forStatement.EmbeddedStatement.Children.Skip(1).First() as BreakStatement; - LabelStatement label = forStatement.EmbeddedStatement.Children.Skip(2).First() as LabelStatement; - if (condition != null && breakStmt != null && label != null && - condition.TrueStatement.Children.Count() == 1) - { - GotoStatement gotoStmt = condition.TrueStatement.FirstChild as GotoStatement; - if (gotoStmt != null && gotoStmt.Label == label.Label) { - condition.Remove(); - breakStmt.Remove(); - forStatement.Condition = condition.Condition; - } - } - } - - // Restore loop condition (version 2) - if (forStatement.Condition.IsNull) { - IfElseStatement condition = forStatement.EmbeddedStatement.FirstChild as IfElseStatement; - if (condition != null && - condition.TrueStatement.Children.Any() && - condition.TrueStatement.FirstChild is BlockStatement && - condition.TrueStatement.Children.Count() == 1 && - condition.TrueStatement.FirstChild.FirstChild is BreakStatement && - condition.FalseStatement.Children.Any() && - condition.FalseStatement.FirstChild is BlockStatement && - condition.FalseStatement.Children.Count() == 0) - { - condition.Remove(); - forStatement.Condition = new UnaryOperatorExpression() { Expression = condition.Condition, Operator = UnaryOperatorType.Not }; - } - } - - // Restore loop iterator - if (forStatement.EmbeddedStatement.Children.Any() && - !forStatement.Iterators.Any()) - { - ExpressionStatement lastStmt = forStatement.EmbeddedStatement.LastChild as ExpressionStatement; - if (lastStmt != null && - (lastStmt.Expression is AssignmentExpression || lastStmt.Expression is UnaryOperatorExpression)) { - lastStmt.Remove(); - forStatement.Iterators.Add(lastStmt); - } - } - - return null; - } - } -} diff --git a/ICSharpCode.Decompiler/Ast/Transforms/TransformationPipeline.cs b/ICSharpCode.Decompiler/Ast/Transforms/TransformationPipeline.cs index 888269437..a559930cb 100644 --- a/ICSharpCode.Decompiler/Ast/Transforms/TransformationPipeline.cs +++ b/ICSharpCode.Decompiler/Ast/Transforms/TransformationPipeline.cs @@ -29,19 +29,6 @@ namespace Decompiler.Transforms { if (node == null) return; - for (int i = 0; i < 4; i++) { - context.CancellationToken.ThrowIfCancellationRequested(); - if (Options.ReduceAstJumps) { - node.AcceptVisitor(new Transforms.Ast.RemoveGotos(), null); - node.AcceptVisitor(new Transforms.Ast.RemoveDeadLabels(), null); - } - if (Options.ReduceAstLoops) { - node.AcceptVisitor(new Transforms.Ast.RestoreLoop(), null); - } - if (Options.ReduceAstOther) { - node.AcceptVisitor(new Transforms.Ast.RemoveEmptyElseBody(), null); - } - } foreach (var transform in CreatePipeline(context)) { context.CancellationToken.ThrowIfCancellationRequested(); diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 48b748732..5621c926c 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -63,10 +63,6 @@ - - - - @@ -93,6 +89,7 @@ + @@ -101,7 +98,6 @@ - diff --git a/ICSharpCode.Decompiler/ILAst/GotoRemoval.cs b/ICSharpCode.Decompiler/ILAst/GotoRemoval.cs new file mode 100644 index 000000000..365dc4464 --- /dev/null +++ b/ICSharpCode.Decompiler/ILAst/GotoRemoval.cs @@ -0,0 +1,202 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Collections.Generic; +using System.Linq; +using Decompiler.ControlFlow; + +namespace Decompiler +{ + public class GotoRemoval + { + Dictionary parent = new Dictionary(); + Dictionary nextSibling = new Dictionary(); + + public void RemoveGotos(ILBlock method) + { + // Build the navigation data + parent[method] = null; + foreach (ILNode node in method.GetSelfAndChildrenRecursive()) { + ILNode previousChild = null; + foreach (ILNode child in node.GetChildren()) { + Debug.Assert(!parent.ContainsKey(child)); + parent[child] = node; + if (previousChild != null) + nextSibling[previousChild] = child; + previousChild = child; + } + if (previousChild != null) + nextSibling[previousChild] = null; + } + + // Simplify gotos + foreach (ILExpression gotoExpr in method.GetSelfAndChildrenRecursive().Where(e => e.Code == ILCode.Br || e.Code == ILCode.Leave)) { + TrySimplifyGoto(gotoExpr); + } + + // Remove dead lables and nops + RemoveDeadLabels(method); + + // Remove redundant continue + foreach(ILWhileLoop loop in method.GetSelfAndChildrenRecursive()) { + var body = loop.BodyBlock.Body; + if (body.Count > 0 && body.Last().Match(ILCode.LoopContinue)) { + body.RemoveAt(body.Count - 1); + } + } + + // Remove redundant return + 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); + } + } + + public static void RemoveDeadLabels(ILNode method) + { + HashSet liveLabels = new HashSet(method.GetSelfAndChildrenRecursive().SelectMany(e => e.GetBranchTargets())); + foreach(ILBlock block in method.GetSelfAndChildrenRecursive().ToList()) { + block.Body = block.Body.Where(n => !n.Match(ILCode.Nop) && !(n is ILLabel && !liveLabels.Contains((ILLabel)n))).ToList(); + } + } + + bool TrySimplifyGoto(ILExpression gotoExpr) + { + Debug.Assert(gotoExpr.Code == ILCode.Br || gotoExpr.Code == ILCode.Leave); + Debug.Assert(gotoExpr.Prefixes == null); + Debug.Assert(gotoExpr.Operand != null); + + ILExpression target = Enter(gotoExpr, new HashSet()); + if (target == null) + return false; + + if (target == Exit(gotoExpr, new HashSet())) { + gotoExpr.Code = ILCode.Nop; + gotoExpr.Operand = null; + target.ILRanges.AddRange(gotoExpr.ILRanges); + gotoExpr.ILRanges.Clear(); + return true; + } + + ILWhileLoop loop = null; + ILNode current = gotoExpr; + while(loop == null && current != null) { + current = parent[current]; + loop = current as ILWhileLoop; + } + + if (loop != null && target == Exit(loop, new HashSet())) { + gotoExpr.Code = ILCode.LoopBreak; + gotoExpr.Operand = null; + return true; + } + + if (loop != null && target == Enter(loop, new HashSet())) { + gotoExpr.Code = ILCode.LoopContinue; + gotoExpr.Operand = null; + return true; + } + + return false; + } + + /// + /// Get the first expression to be excecuted if the instruction pointer is at the start of the given node + /// + ILExpression Enter(ILNode node, HashSet 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) { + return Enter((ILLabel)expr.Operand, visitedNodes); + } else if (expr.Code == ILCode.Nop) { + return Exit(expr, visitedNodes); + } 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 Enter(tryCatch.TryBlock, visitedNodes); + } + + ILSwitch ilSwitch = node as ILSwitch; + if (ilSwitch != null) { + return ilSwitch.Condition; + } + + throw new NotSupportedException(node.GetType().ToString()); + } + + /// + /// Get the first expression to be excecuted if the instruction pointer is at the end of the given node + /// + ILExpression Exit(ILNode node, HashSet 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 || + nodeParent is ILTryCatchBlock || + nodeParent is ILSwitch) + { + return Exit(nodeParent, visitedNodes); + } + + if (nodeParent is ILWhileLoop) { + return Enter(nodeParent, visitedNodes); + } + + throw new NotSupportedException(nodeParent.GetType().ToString()); + } + } +} diff --git a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs index 6fa5b760e..668644821 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs @@ -2,10 +2,10 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; - using ICSharpCode.Decompiler.FlowAnalysis; using Mono.Cecil; using Mono.Cecil.Cil; +using Mono.CSharp; namespace Decompiler.ControlFlow { @@ -16,8 +16,8 @@ namespace Decompiler.ControlFlow FindLoops, FindConditions, FlattenNestedMovableBlocks, - SimpleGotoRemoval, - RemoveDeadLabels, + GotoRemoval, + DuplicateReturns, HandleArrayInitializers, TypeInference, None @@ -25,6 +25,8 @@ namespace Decompiler.ControlFlow public class ILAstOptimizer { + int nextLabelIndex = 0; + Dictionary labelToCfNode = new Dictionary(); public void Optimize(DecompilerContext context, ILBlock method, ILAstOptimizationStep abortBeforeStep = ILAstOptimizationStep.None) @@ -64,11 +66,12 @@ namespace Decompiler.ControlFlow if (abortBeforeStep == ILAstOptimizationStep.FlattenNestedMovableBlocks) return; FlattenBasicBlocks(method); - if (abortBeforeStep == ILAstOptimizationStep.SimpleGotoRemoval) return; - SimpleGotoRemoval(method); + if (abortBeforeStep == ILAstOptimizationStep.GotoRemoval) return; + new GotoRemoval().RemoveGotos(method); - if (abortBeforeStep == ILAstOptimizationStep.RemoveDeadLabels) return; - RemoveDeadLabels(method); + if (abortBeforeStep == ILAstOptimizationStep.DuplicateReturns) return; + DuplicateReturnStatements(method); + GotoRemoval.RemoveDeadLabels(method); if (abortBeforeStep == ILAstOptimizationStep.HandleArrayInitializers) return; ArrayInitializers.Transform(method); @@ -77,8 +80,6 @@ namespace Decompiler.ControlFlow TypeAnalysis.Run(context, method); } - int nextBlockIndex = 0; - /// /// Group input into a set of blocks that can be later arbitraliby schufled. /// The method adds necessary branches to make control flow between blocks @@ -93,7 +94,7 @@ namespace Decompiler.ControlFlow List basicBlocks = new List(); ILBasicBlock basicBlock = new ILBasicBlock() { - EntryLabel = new ILLabel() { Name = "Block_" + (nextBlockIndex++) } + EntryLabel = new ILLabel() { Name = "Block_" + (nextLabelIndex++) } }; basicBlocks.Add(basicBlock); block.EntryGoto = new ILExpression(ILCode.Br, basicBlock.EntryLabel); @@ -120,7 +121,7 @@ namespace Decompiler.ControlFlow // Insert as entry label basicBlock.EntryLabel = (ILLabel)currNode; } else { - basicBlock.EntryLabel = new ILLabel() { Name = "Block_" + (nextBlockIndex++) }; + basicBlock.EntryLabel = new ILLabel() { Name = "Block_" + (nextLabelIndex++) }; basicBlock.Body.Add(currNode); } @@ -176,11 +177,12 @@ namespace Decompiler.ControlFlow do { modified = false; for (int i = 0; i < block.Body.Count;) { - if (TrySimplifyShortCircuit(block.Body, (ILBasicBlock)block.Body[i])) { + ILBasicBlock bb = (ILBasicBlock)block.Body[i]; + if (TrySimplifyShortCircuit(block.Body, bb)) { modified = true; continue; } - if (TrySimplifyTernaryOperator(block.Body, (ILBasicBlock)block.Body[i])) { + if (TrySimplifyTernaryOperator(block.Body, bb)) { modified = true; continue; } @@ -323,6 +325,53 @@ namespace Decompiler.ControlFlow return false; } + void DuplicateReturnStatements(ILBlock method) + { + Dictionary nextSibling = new Dictionary(); + + // Build navigation data + foreach(ILBlock block in method.GetSelfAndChildrenRecursive()) { + 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()) { + 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; + ILExpression retExpr; + if (nextSibling.TryGetValue(targetLabel, out target) && + target.Match(ILCode.Ret, out retExpr)) + { + ILVariable locVar; + object constValue; + if (retExpr.Arguments.Count == 0) { + block.Body[i] = new ILExpression(ILCode.Ret, null); + } else if (retExpr.Arguments.Single().Match(ILCode.Ldloc, out locVar)) { + block.Body[i] = new ILExpression(ILCode.Ret, null, new ILExpression(ILCode.Ldloc, locVar)); + } else if (retExpr.Arguments.Single().Match(ILCode.Ldc_I4, out constValue)) { + block.Body[i] = new ILExpression(ILCode.Ret, null, new ILExpression(ILCode.Ldc_I4, constValue)); + } + } + } + } + } + } + ControlFlowGraph BuildGraph(List nodes, ILLabel entryLabel) { int index = 0; @@ -400,33 +449,47 @@ namespace Decompiler.ControlFlow { HashSet loopContents = FindLoopContent(scope, node); - ILWhileLoop loop = new ILWhileLoop(); - - ILBasicBlock basicBlock = node.UserData as ILBasicBlock; + ILBasicBlock basicBlock = (ILBasicBlock)node.UserData; ILExpression branchExpr = null; ILLabel trueLabel = null; ILLabel falseLabel = null; - if(basicBlock != null && IsConditionalBranch(basicBlock, ref branchExpr, ref trueLabel, ref falseLabel)) { + if(IsConditionalBranch(basicBlock, ref branchExpr, ref trueLabel, ref falseLabel)) { loopContents.Remove(node); scope.Remove(node); branchExpr.Operand = null; // Do not keep label alive - // Use loop to implement condition - loop.Condition = branchExpr; - loop.PreLoopLabel = basicBlock.EntryLabel; - loop.PostLoopGoto = new ILExpression(ILCode.Br, falseLabel); - loop.BodyBlock = new ILBlock() { EntryGoto = new ILExpression(ILCode.Br, trueLabel) }; + // Use loop to implement the condition + result.Add(new ILBasicBlock() { + EntryLabel = basicBlock.EntryLabel, + Body = new List() { + new ILWhileLoop() { + Condition = branchExpr, + BodyBlock = new ILBlock() { + EntryGoto = new ILExpression(ILCode.Br, trueLabel), + Body = FindLoops(loopContents, node, true) + } + }, + new ILExpression(ILCode.Br, falseLabel) + }, + FallthoughGoto = null + }); } else { - // Give the block some explicit entry point - ILLabel entryLabel = new ILLabel() { Name = "Loop_" + (nextBlockIndex++) }; - loop.BodyBlock = new ILBlock() { EntryGoto = new ILExpression(ILCode.Br, entryLabel) }; - ((ILBasicBlock)node.UserData).Body.Insert(0, entryLabel); + result.Add(new ILBasicBlock() { + EntryLabel = new ILLabel() { Name = "Loop_" + (nextLabelIndex++) }, + Body = new List() { + new ILWhileLoop() { + BodyBlock = new ILBlock() { + EntryGoto = new ILExpression(ILCode.Br, basicBlock.EntryLabel), + Body = FindLoops(loopContents, node, true) + } + }, + }, + FallthoughGoto = null + }); } - loop.BodyBlock.Body = FindLoops(loopContents, node, true); // Move the content into loop block scope.ExceptWith(loopContents); - result.Add(loop); } // Using the dominator tree should ensure we find the the widest loop first @@ -690,44 +753,31 @@ namespace Decompiler.ControlFlow } } } + } + + public static class ILAstOptimizerExtensionMethods + { + public static bool Match(this ILNode node, ILCode code) + { + ILExpression expr = node as ILExpression; + return expr != null && expr.Prefixes == null && expr.Code == code; + } - void SimpleGotoRemoval(ILBlock ast) + public static bool Match(this ILNode node, ILCode code, out ILExpression expr) { - // TODO: Assign IL ranges from br to something else - var blocks = ast.GetSelfAndChildrenRecursive().ToList(); - foreach(ILBlock block in blocks) { - for (int i = 0; i < block.Body.Count; i++) { - ILExpression expr = block.Body[i] as ILExpression; - // Uncoditional branch - if (expr != null && (expr.Code == ILCode.Br)) { - // Check that branch is followed by its label (allow multiple labels) - for (int j = i + 1; j < block.Body.Count; j++) { - ILLabel label = block.Body[j] as ILLabel; - if (label == null) - break; // Can not optimize - if (expr.Operand == label) { - block.Body.RemoveAt(i); - break; // Branch removed - } - } - } - } - } + expr = node as ILExpression; + return expr != null && expr.Prefixes == null && expr.Code == code; } - void RemoveDeadLabels(ILBlock ast) + public static bool Match(this ILNode node, ILCode code, out T operand) { - HashSet liveLabels = new HashSet(ast.GetSelfAndChildrenRecursive().SelectMany(e => e.GetBranchTargets())); - var blocks = ast.GetSelfAndChildrenRecursive().ToList(); - foreach(ILBlock block in blocks) { - for (int i = 0; i < block.Body.Count;) { - ILLabel label = block.Body[i] as ILLabel; - if (label != null && !liveLabels.Contains(label)) { - block.Body.RemoveAt(i); - } else { - i++; - } - } + ILExpression expr = node as ILExpression; + if (expr != null && expr.Prefixes == null && expr.Code == code) { + operand = (T)expr.Operand; + return true; + } else { + operand = default(T); + return false; } } } diff --git a/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs b/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs index 24fb62672..ec5aeb2b1 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs @@ -18,7 +18,19 @@ namespace Decompiler { public IEnumerable GetSelfAndChildrenRecursive() where T: ILNode { - return TreeTraversal.PreOrder(this, c => c != null ? c.GetChildren() : null).OfType(); + List result = new List(16); + AccumulateSelfAndChildrenRecursive(result); + return result; + } + + void AccumulateSelfAndChildrenRecursive(List list) where T:ILNode + { + if (this is T) + list.Add((T)this); + foreach (ILNode node in this.GetChildren()) { + if (node != null) + node.AccumulateSelfAndChildrenRecursive(list); + } } public virtual IEnumerable GetChildren() @@ -360,27 +372,19 @@ namespace Decompiler public class ILWhileLoop : ILNode { - public ILLabel PreLoopLabel; // Label allowing to jump to condition public ILExpression Condition; - public ILBlock BodyBlock; // BodyBlock.EntryGoto performs the goto for a met condition - public ILExpression PostLoopGoto; // Performs the goto for a failed condition + public ILBlock BodyBlock; public override IEnumerable GetChildren() { - if (this.PreLoopLabel != null) - yield return this.PreLoopLabel; if (this.Condition != null) yield return this.Condition; if (this.BodyBlock != null) yield return this.BodyBlock; - if (this.PostLoopGoto != null) - yield return this.PostLoopGoto; } public override void WriteTo(ITextOutput output) { - if (this.PreLoopLabel != null) - this.PreLoopLabel.WriteTo(output); output.WriteLine(""); output.Write("loop ("); if (this.Condition != null) @@ -390,8 +394,6 @@ namespace Decompiler this.BodyBlock.WriteTo(output); output.Unindent(); output.WriteLine("}"); - if (this.PostLoopGoto != null) - this.PostLoopGoto.WriteTo(output); } } diff --git a/ICSharpCode.Decompiler/ILAst/ILCodes.cs b/ICSharpCode.Decompiler/ILAst/ILCodes.cs index 75f9b1239..4871d0f40 100644 --- a/ICSharpCode.Decompiler/ILAst/ILCodes.cs +++ b/ICSharpCode.Decompiler/ILAst/ILCodes.cs @@ -260,6 +260,8 @@ namespace Decompiler BrLogicOr, InitArray, // Array Initializer TernaryOp, // ?: + LoopBreak, + LoopContinue, Pattern // used for ILAst pattern nodes } diff --git a/ICSharpCode.Decompiler/Options.cs b/ICSharpCode.Decompiler/Options.cs deleted file mode 100644 index 3d8e6e584..000000000 --- a/ICSharpCode.Decompiler/Options.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Decompiler -{ - public static class Options - { - public static readonly bool NodeComments = false; - public static readonly bool ReduceLoops = true; - public static readonly bool ReduceConditonals = true; - public static readonly bool ReduceAstJumps = true; - public static readonly bool ReduceAstLoops = true; - public static readonly bool ReduceAstOther = true; - } -}