diff --git a/Debugger/ILSpy.Debugger/AvalonEdit/IconBarMargin.cs b/Debugger/ILSpy.Debugger/AvalonEdit/IconBarMargin.cs index d41814054..77ff1c8c6 100644 --- a/Debugger/ILSpy.Debugger/AvalonEdit/IconBarMargin.cs +++ b/Debugger/ILSpy.Debugger/AvalonEdit/IconBarMargin.cs @@ -25,10 +25,8 @@ using System.Windows.Media; using ICSharpCode.AvalonEdit.Editing; using ICSharpCode.AvalonEdit.Rendering; using ICSharpCode.AvalonEdit.Utils; -using ICSharpCode.Decompiler.Disassembler; using ILSpy.Debugger.Bookmarks; using ILSpy.Debugger.Services; -using ILSpy.Debugger.ToolTips; using Mono.Cecil; namespace ILSpy.Debugger.AvalonEdit @@ -74,7 +72,7 @@ namespace ILSpy.Debugger.AvalonEdit // create a dictionary line number => first bookmark Dictionary<int, BookmarkBase> bookmarkDict = new Dictionary<int, BookmarkBase>(); foreach (var bm in BookmarkManager.Bookmarks) { - if (IconBarMargin.CurrentType == null || bm.TypeName != IconBarMargin.CurrentType.FullName) + if (CurrentType == null || bm.TypeName != CurrentType.FullName) continue; int line = bm.LineNumber; @@ -88,10 +86,44 @@ namespace ILSpy.Debugger.AvalonEdit BookmarkBase bm; if (bookmarkDict.TryGetValue(lineNumber, out bm)) { Rect rect = new Rect(0, PixelSnapHelpers.Round(line.VisualTop - textView.VerticalOffset, pixelSize.Height), 16, 16); - drawingContext.DrawImage(bm.Image, rect); + if (dragDropBookmark == bm && dragStarted) + drawingContext.PushOpacity(0.5); + drawingContext.DrawImage(bm.Image, rect); + if (dragDropBookmark == bm && dragStarted) + drawingContext.Pop(); } - } + } + if (dragDropBookmark != null && dragStarted) { + Rect rect = new Rect(0, PixelSnapHelpers.Round(dragDropCurrentPoint - 8, pixelSize.Height), 16, 16); + drawingContext.DrawImage(dragDropBookmark.Image, rect); + } + } + } + + IBookmark dragDropBookmark; // bookmark being dragged (!=null if drag'n'drop is active) + double dragDropStartPoint; + double dragDropCurrentPoint; + bool dragStarted; // whether drag'n'drop operation has started (mouse was moved minimum distance) + + protected override void OnMouseDown(MouseButtonEventArgs e) + { + base.OnMouseDown(e); + int line = GetLineFromMousePosition(e); + if (!e.Handled && line > 0) { + IBookmark bm = GetBookmarkFromLine(line); + if (bm != null) { + bm.MouseDown(e); + if (!e.Handled) { + if (e.ChangedButton == MouseButton.Left && bm.CanDragDrop && CaptureMouse()) { + StartDragDrop(bm, e); + e.Handled = true; + } + } + } } + // don't allow selecting text through the IconBarMargin + if (e.ChangedButton == MouseButton.Left) + e.Handled = true; } BookmarkBase GetBookmarkFromLine(int line) @@ -99,8 +131,8 @@ namespace ILSpy.Debugger.AvalonEdit BookmarkBase result = null; foreach (BookmarkBase bm in BookmarkManager.Bookmarks) { if (bm.LineNumber == line && - IconBarMargin.CurrentType != null && - bm.TypeName == IconBarMargin.CurrentType.FullName) { + CurrentType != null && + bm.TypeName == CurrentType.FullName) { if (result == null || bm.ZOrder > result.ZOrder) result = bm; } @@ -108,6 +140,46 @@ namespace ILSpy.Debugger.AvalonEdit return result; } + protected override void OnLostMouseCapture(MouseEventArgs e) + { + CancelDragDrop(); + base.OnLostMouseCapture(e); + } + + void StartDragDrop(IBookmark bm, MouseEventArgs e) + { + dragDropBookmark = bm; + dragDropStartPoint = dragDropCurrentPoint = e.GetPosition(this).Y; + if (TextView != null) { + TextArea area = TextView.Services.GetService(typeof(TextArea)) as TextArea; + if (area != null) + area.PreviewKeyDown += TextArea_PreviewKeyDown; + } + } + + void CancelDragDrop() + { + if (dragDropBookmark != null) { + dragDropBookmark = null; + dragStarted = false; + if (TextView != null) { + TextArea area = TextView.Services.GetService(typeof(TextArea)) as TextArea; + if (area != null) + area.PreviewKeyDown -= TextArea_PreviewKeyDown; + } + ReleaseMouseCapture(); + InvalidateVisual(); + } + } + + void TextArea_PreviewKeyDown(object sender, KeyEventArgs e) + { + // any key press cancels drag'n'drop + CancelDragDrop(); + if (e.Key == Key.Escape) + e.Handled = true; + } + int GetLineFromMousePosition(MouseEventArgs e) { TextView textView = this.TextView; @@ -119,10 +191,29 @@ namespace ILSpy.Debugger.AvalonEdit return vl.FirstDocumentLine.LineNumber; } + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + if (dragDropBookmark != null) { + dragDropCurrentPoint = e.GetPosition(this).Y; + if (Math.Abs(dragDropCurrentPoint - dragDropStartPoint) > SystemParameters.MinimumVerticalDragDistance) + dragStarted = true; + InvalidateVisual(); + } + } + protected override void OnMouseUp(MouseButtonEventArgs e) { base.OnMouseUp(e); int line = GetLineFromMousePosition(e); + if (!e.Handled && dragDropBookmark != null) { + if (dragStarted) { + if (line != 0) + dragDropBookmark.Drop(line); + e.Handled = true; + } + CancelDragDrop(); + } if (!e.Handled && line != 0) { IBookmark bm = GetBookmarkFromLine(line); if (bm != null) { @@ -144,4 +235,4 @@ namespace ILSpy.Debugger.AvalonEdit } } } -} +} \ No newline at end of file diff --git a/Decompiler/.gitignore b/Decompiler/.gitignore deleted file mode 100644 index c75695746..000000000 --- a/Decompiler/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -bin/ -obj/ \ No newline at end of file diff --git a/Decompiler/tests/QuickSort/AssemblyInfo.cs b/Decompiler/tests/QuickSort/AssemblyInfo.cs deleted file mode 100644 index 0386c7e48..000000000 --- a/Decompiler/tests/QuickSort/AssemblyInfo.cs +++ /dev/null @@ -1,31 +0,0 @@ -#region Using directives - -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -#endregion - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("QuickSort")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("QuickSort")] -[assembly: AssemblyCopyright("")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// This sets the default COM visibility of types in the assembly to invisible. -// If you need to expose a type to COM, use [ComVisible(true)] on that type. -[assembly: ComVisible(false)] - -// The assembly version has following format : -// -// Major.Minor.Build.Revision -// -// You can specify all the values or you can use the default the Revision and -// Build Numbers by using the '*' as shown below: -[assembly: AssemblyVersion("1.0.*")] diff --git a/Decompiler/tests/QuickSort/Program.cs b/Decompiler/tests/QuickSort/Program.cs deleted file mode 100644 index 4cb22fe3b..000000000 --- a/Decompiler/tests/QuickSort/Program.cs +++ /dev/null @@ -1,49 +0,0 @@ -static class QuickSortProgram -{ - public static void Main(string[] args) - { - int[] intArray = new int[args.Length]; - for (int i = 0; i < intArray.Length; i++) { - intArray[i] = int.Parse(args[i]); - } - QuickSort(intArray, 0, intArray.Length - 1); - for (int i = 0; i < intArray.Length; i++) { - System.Console.Write(intArray[i].ToString() + " "); - } - } - - /// For description of this algorithm see: - /// http://en.wikipedia.org/wiki/Quick_sort - public static void QuickSort(int[] array, int left, int right) - { - if (right > left) { - int pivotIndex = (left + right) / 2; - int pivotNew = Partition(array, left, right, pivotIndex); - QuickSort(array, left, pivotNew - 1); - QuickSort(array, pivotNew + 1, right); - } - } - - static int Partition(int[] array, int left, int right, - int pivotIndex) - { - int pivotValue = array[pivotIndex]; - Swap(array, pivotIndex, right); - int storeIndex = left; - for(int i = left; i < right; i++) { - if (array[i] <= pivotValue) { - Swap(array, storeIndex, i); - storeIndex = storeIndex + 1; - } - } - Swap(array, right, storeIndex); - return storeIndex; - } - - static void Swap(int[] array, int index1, int index2) - { - int tmp = array[index1]; - array[index1] = array[index2]; - array[index2] = tmp; - } -} \ No newline at end of file diff --git a/Decompiler/tests/QuickSort/QuickSort.csproj b/Decompiler/tests/QuickSort/QuickSort.csproj deleted file mode 100644 index 191c2a69c..000000000 --- a/Decompiler/tests/QuickSort/QuickSort.csproj +++ /dev/null @@ -1,36 +0,0 @@ -<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <PropertyGroup> - <ProjectGuid>{9C2A1C63-38EA-42CA-BDDD-7D80D9592893}</ProjectGuid> - <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> - <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> - <OutputType>Exe</OutputType> - <RootNamespace>QuickSort</RootNamespace> - <AssemblyName>QuickSort</AssemblyName> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> - <OutputPath>bin\Debug\</OutputPath> - <DebugSymbols>True</DebugSymbols> - <DebugType>Full</DebugType> - <Optimize>False</Optimize> - <CheckForOverflowUnderflow>True</CheckForOverflowUnderflow> - <DefineConstants>DEBUG;TRACE</DefineConstants> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)' == 'Release' "> - <OutputPath>bin\Release\</OutputPath> - <DebugSymbols>False</DebugSymbols> - <DebugType>None</DebugType> - <Optimize>True</Optimize> - <CheckForOverflowUnderflow>False</CheckForOverflowUnderflow> - <DefineConstants>TRACE</DefineConstants> - </PropertyGroup> - <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.Targets" /> - <ItemGroup> - <Reference Include="System" /> - <Reference Include="System.Data" /> - <Reference Include="System.Xml" /> - </ItemGroup> - <ItemGroup> - <Compile Include="AssemblyInfo.cs" /> - <Compile Include="Program.cs" /> - </ItemGroup> -</Project> \ No newline at end of file diff --git a/Decompiler/tests/QuickSort/QuickSort.sln b/Decompiler/tests/QuickSort/QuickSort.sln deleted file mode 100644 index 8ee47e87f..000000000 --- a/Decompiler/tests/QuickSort/QuickSort.sln +++ /dev/null @@ -1,18 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 9.00 -# Visual Studio 2005 -# SharpDevelop 3.0.0.2707 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickSort", "QuickSort.csproj", "{9C2A1C63-38EA-42CA-BDDD-7D80D9592893}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {9C2A1C63-38EA-42CA-BDDD-7D80D9592893}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9C2A1C63-38EA-42CA-BDDD-7D80D9592893}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9C2A1C63-38EA-42CA-BDDD-7D80D9592893}.Release|Any CPU.Build.0 = Release|Any CPU - {9C2A1C63-38EA-42CA-BDDD-7D80D9592893}.Release|Any CPU.ActiveCfg = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/Decompiler/tests/QuickSort/bin/Debug/QuickSort.exe b/Decompiler/tests/QuickSort/bin/Debug/QuickSort.exe deleted file mode 100644 index 7e3f98ac5..000000000 Binary files a/Decompiler/tests/QuickSort/bin/Debug/QuickSort.exe and /dev/null differ diff --git a/Decompiler/tests/QuickSort/bin/Debug/QuickSort.pdb b/Decompiler/tests/QuickSort/bin/Debug/QuickSort.pdb deleted file mode 100644 index 0ef5b30bf..000000000 Binary files a/Decompiler/tests/QuickSort/bin/Debug/QuickSort.pdb and /dev/null differ diff --git a/Decompiler/tests/Stack/StackTests.exe b/Decompiler/tests/Stack/StackTests.exe deleted file mode 100644 index db6194ad6..000000000 Binary files a/Decompiler/tests/Stack/StackTests.exe and /dev/null differ diff --git a/Decompiler/tests/Stack/StackTests.il b/Decompiler/tests/Stack/StackTests.il deleted file mode 100644 index 78d50bc19..000000000 --- a/Decompiler/tests/Stack/StackTests.il +++ /dev/null @@ -1,135 +0,0 @@ -.assembly extern mscorlib -{ - .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) - .ver 4:0:0:0 -} -.assembly StackTests -{ - .hash algorithm 0x00008004 - .ver 1:0:4059:39717 -} -.module StackTests.exe -.imagebase 0x00400000 -.file alignment 0x00000200 -.stackreserve 0x00100000 -.subsystem 0x0003 // WINDOWS_CUI -.corflags 0x00000003 // ILONLY 32BITREQUIRED - -.class private auto ansi beforefieldinit StackTests.Program extends [mscorlib]System.Object -{ - .method public hidebysig static void Main(string[] args) cil managed - { - .entrypoint - .maxstack 8 - - ldc.i4.0 - call string StackTests.Program::Test1(bool cond) - call void [mscorlib]System.Console::WriteLine(string) // false - - ldc.i4.1 - call string StackTests.Program::Test1(bool cond) - call void [mscorlib]System.Console::WriteLine(string) // true - - ldc.i4.0 - ldc.i4.0 - ldc.i4.0 - call int32 StackTests.Program::Test2(int32 switch1, int32 br1, int32 br2) - call void [mscorlib]System.Console::WriteLine(int32) // 11 - - ldc.i4.0 - ldc.i4.1 - ldc.i4.0 - call int32 StackTests.Program::Test2(int32 switch1, int32 br1, int32 br2) - call void [mscorlib]System.Console::WriteLine(int32) // 21 - - ldc.i4.1 - ldc.i4.1 - ldc.i4.1 - call int32 StackTests.Program::Test2(int32 switch1, int32 br1, int32 br2) - call void [mscorlib]System.Console::WriteLine(int32) // 32 - - ldc.i4.2 - ldc.i4.1 - ldc.i4.0 - call int32 StackTests.Program::Test2(int32 switch1, int32 br1, int32 br2) - call void [mscorlib]System.Console::WriteLine(int32) // 23 - - ret - } - - .method public hidebysig static string Test1(bool cond) cil managed - { - ldarg.0 - brtrue TRUE - - FALSE: - ldstr "false" - br EXIT - - TRUE: - ldstr "true" - - EXIT: - ret - } - - .method public hidebysig static int32 Test2(int32 switch1, int32 br1, int32 br2) cil managed - { - ldarg.0 - switch (ENTRY1, ENTRY2, ENTRY3) - ldc.i4.0 - ret - - ENTRY1: - ldc.i4.1 - br BRANCH1 - - ENTRY2: - ldc.i4.2 - br BRANCH1 - - ENTRY3: - ldc.i4.3 - br BRANCH2 - - BRANCH1: - ldarg.1 - brtrue BRANCH2 - - EXIT1: - ldc.i4 10 - add - ret - - BRANCH2: - ldarg.2 - brtrue.s EXIT3 - - EXIT2: - ldc.i4 20 - add - ret - - EXIT3: - ldc.i4 30 - add - ret - } - - .method public hidebysig specialname rtspecialname - instance void .ctor() cil managed - { - // Code size 7 (0x7) - .maxstack 8 - IL_0000: ldarg.0 - IL_0001: call instance void [mscorlib]System.Object::.ctor() - IL_0006: ret - } // end of method Program::.ctor - -} // end of class StackTests.Program - - -// ============================================================= - -// *********** DISASSEMBLY COMPLETE *********************** -// WARNING: Created Win32 resource file D:\git\ILSpy\Decompiler\tests\Stack\StackTests.res diff --git a/ICSharpCode.Decompiler/Ast/AstBuilder.cs b/ICSharpCode.Decompiler/Ast/AstBuilder.cs index 2e488f385..5116658a5 100644 --- a/ICSharpCode.Decompiler/Ast/AstBuilder.cs +++ b/ICSharpCode.Decompiler/Ast/AstBuilder.cs @@ -17,28 +17,12 @@ namespace Decompiler public void GenerateCode(ITextOutput output) { - for (int i = 0; i < 4; i++) { - if (Options.ReduceAstJumps) { - //astCompileUnit.AcceptVisitor(new Transforms.Ast.RemoveGotos(), null); - //astCompileUnit.AcceptVisitor(new Transforms.Ast.RemoveDeadLabels(), null); - } - if (Options.ReduceAstLoops) { - //astCompileUnit.AcceptVisitor(new Transforms.Ast.RestoreLoop(), null); - } - if (Options.ReduceAstOther) { - astCompileUnit.AcceptVisitor(new Transforms.Ast.Idioms(), null); - astCompileUnit.AcceptVisitor(new Transforms.Ast.RemoveEmptyElseBody(), null); - astCompileUnit.AcceptVisitor(new Transforms.Ast.PushNegation(), null); - } - } - if (Options.ReduceAstOther) { - astCompileUnit.AcceptVisitor(new Transforms.Ast.SimplifyTypeReferences(), null); - astCompileUnit.AcceptVisitor(new Transforms.Ast.Idioms(), null); - } - if (Options.ReduceAstLoops) { - //astCompileUnit.AcceptVisitor(new Transforms.Ast.RestoreLoop(), null); - } - + GenerateCode(output, null); + } + + public void GenerateCode(ITextOutput output, Predicate<IAstVisitor<object, object>> transformAbortCondition) + { + Transforms.TransformationPipeline.RunTransformationsUntil(astCompileUnit, transformAbortCondition); astCompileUnit.AcceptVisitor(new InsertParenthesesVisitor { InsertParenthesesForReadability = true }, null); var outputFormatter = new TextOutputFormatter(output); @@ -263,14 +247,18 @@ namespace Decompiler } name = ICSharpCode.NRefactory.TypeSystem.ReflectionHelper.SplitTypeParameterCountFromReflectionName(name); - if (ns.Length == 0) - return new SimpleType(name); - string[] parts = ns.Split('.'); - AstType nsType = new SimpleType(parts[0]); - for (int i = 1; i < parts.Length; i++) { - nsType = new MemberType { Target = nsType, MemberName = parts[i] }; - } - return new MemberType { Target = nsType, MemberName = name }.WithAnnotation(type); + + // TODO: Until we can simplify type with 'using', use just the name without namesapce + return new SimpleType(name).WithAnnotation(type); + +// if (ns.Length == 0) +// return new SimpleType(name).WithAnnotation(type); +// string[] parts = ns.Split('.'); +// AstType nsType = new SimpleType(parts[0]); +// for (int i = 1; i < parts.Length; i++) { +// nsType = new MemberType { Target = nsType, MemberName = parts[i] }; +// } +// return new MemberType { Target = nsType, MemberName = name }.WithAnnotation(type); } } } @@ -487,7 +475,7 @@ namespace Decompiler return astField; } - IEnumerable<ParameterDeclaration> MakeParameters(IEnumerable<ParameterDefinition> paramCol) + public static IEnumerable<ParameterDeclaration> MakeParameters(IEnumerable<ParameterDefinition> paramCol) { foreach(ParameterDefinition paramDef in paramCol) { ParameterDeclaration astParam = new ParameterDeclaration(); @@ -502,4 +490,4 @@ namespace Decompiler } } } -} \ No newline at end of file +} diff --git a/ICSharpCode.Decompiler/Ast/AstMetodBodyBuilder.cs b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs similarity index 74% rename from ICSharpCode.Decompiler/Ast/AstMetodBodyBuilder.cs rename to ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs index 4e266680d..1f131d3b5 100644 --- a/ICSharpCode.Decompiler/Ast/AstMetodBodyBuilder.cs +++ b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs @@ -15,8 +15,7 @@ namespace Decompiler public class AstMethodBodyBuilder { MethodDefinition methodDef; - static Dictionary<string, Cecil.TypeReference> localVarTypes = new Dictionary<string, Cecil.TypeReference>(); - static Dictionary<string, bool> localVarDefined = new Dictionary<string, bool>(); + HashSet<ILVariable> definedLocalVars = new HashSet<ILVariable>(); public static BlockStatement CreateMethodBody(MethodDefinition methodDef) { @@ -47,48 +46,45 @@ namespace Decompiler if (methodDef.Body == null) return null; ILBlock ilMethod = new ILBlock(); - ilMethod.Body = new ILAstBuilder().Build(methodDef, true); + ILAstBuilder astBuilder = new ILAstBuilder(); + ilMethod.Body = astBuilder.Build(methodDef, true); ILAstOptimizer bodyGraph = new ILAstOptimizer(); bodyGraph.Optimize(ilMethod); List<string> intNames = new List<string>(new string[] {"i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t"}); Dictionary<string, int> typeNames = new Dictionary<string, int>(); - foreach(VariableDefinition varDef in methodDef.Body.Variables) { - if (string.IsNullOrEmpty(varDef.Name)) { - if (varDef.VariableType.FullName == Constants.Int32 && intNames.Count > 0) { - varDef.Name = intNames[0]; - intNames.RemoveAt(0); - } else { - string name; - if (varDef.VariableType.IsArray) { - name = "array"; - } else if (!typeNameToVariableNameDict.TryGetValue(varDef.VariableType.FullName, out name)) { - name = varDef.VariableType.Name; - // remove the 'I' for interfaces - if (name.Length >= 3 && name[0] == 'I' && char.IsUpper(name[1]) && char.IsLower(name[2])) - name = name.Substring(1); - // remove the backtick (generics) - int pos = name.IndexOf('`'); - if (pos >= 0) - name = name.Substring(0, pos); - if (name.Length == 0) - name = "obj"; - else - name = char.ToLower(name[0]) + name.Substring(1); - } - if (!typeNames.ContainsKey(name)) { - typeNames.Add(name, 0); - } - int count = typeNames[name]; - if (count > 0) { - name += count.ToString(); - } - varDef.Name = name; + foreach(ILVariable varDef in astBuilder.Variables) { + if (varDef.Type.FullName == Constants.Int32 && intNames.Count > 0) { + varDef.Name = intNames[0]; + intNames.RemoveAt(0); + } else { + string name; + if (varDef.Type.IsArray) { + name = "array"; + } else if (!typeNameToVariableNameDict.TryGetValue(varDef.Type.FullName, out name)) { + name = varDef.Type.Name; + // remove the 'I' for interfaces + if (name.Length >= 3 && name[0] == 'I' && char.IsUpper(name[1]) && char.IsLower(name[2])) + name = name.Substring(1); + // remove the backtick (generics) + int pos = name.IndexOf('`'); + if (pos >= 0) + name = name.Substring(0, pos); + if (name.Length == 0) + name = "obj"; + else + name = char.ToLower(name[0]) + name.Substring(1); } + if (!typeNames.ContainsKey(name)) { + typeNames.Add(name, 0); + } + int count = ++(typeNames[name]); + if (count > 1) { + name += count.ToString(); + } + varDef.Name = name; } - localVarTypes[varDef.Name] = varDef.VariableType; - localVarDefined[varDef.Name] = false; // Ast.VariableDeclaration astVar = new Ast.VariableDeclaration(varDef.Name); // Ast.LocalVariableDeclaration astLocalVar = new Ast.LocalVariableDeclaration(astVar); @@ -117,8 +113,10 @@ namespace Decompiler if (node is ILLabel) { yield return new Ast.LabelStatement { Label = ((ILLabel)node).Name }; } else if (node is ILExpression) { - object codeExpr = TransformExpression((ILExpression)node); + List<ILRange> ilRanges = ((ILExpression)node).GetILRanges(); + AstNode codeExpr = TransformExpression((ILExpression)node); if (codeExpr != null) { + codeExpr = codeExpr.WithAnnotation(ilRanges); if (codeExpr is Ast.Expression) { yield return new Ast.ExpressionStatement { Expression = (Ast.Expression)codeExpr }; } else if (codeExpr is Ast.Statement) { @@ -151,12 +149,30 @@ namespace Decompiler */ } else if (node is ILCondition) { ILCondition conditionalNode = (ILCondition)node; - // Swap bodies - yield return new Ast.IfElseStatement { - Condition = new UnaryOperatorExpression(UnaryOperatorType.Not, MakeBranchCondition(conditionalNode.Condition)), - TrueStatement = TransformBlock(conditionalNode.FalseBlock), - FalseStatement = TransformBlock(conditionalNode.TrueBlock) - }; + if (conditionalNode.FalseBlock.Body.Any()) { + // Swap bodies + yield return new Ast.IfElseStatement { + Condition = new UnaryOperatorExpression(UnaryOperatorType.Not, MakeBranchCondition(conditionalNode.Condition)), + TrueStatement = TransformBlock(conditionalNode.FalseBlock), + FalseStatement = TransformBlock(conditionalNode.TrueBlock) + }; + } else { + yield return new Ast.IfElseStatement { + Condition = MakeBranchCondition(conditionalNode.Condition), + TrueStatement = TransformBlock(conditionalNode.TrueBlock), + FalseStatement = TransformBlock(conditionalNode.FalseBlock) + }; + } + } else if (node is ILSwitch) { + ILSwitch ilSwitch = (ILSwitch)node; + SwitchStatement switchStmt = new SwitchStatement() { Expression = (Expression)TransformExpression(ilSwitch.Condition.Arguments[0]) }; + for (int i = 0; i < ilSwitch.CaseBlocks.Count; i++) { + switchStmt.AddChild(new SwitchSection() { + CaseLabels = new[] { new CaseLabel() { Expression = new PrimitiveExpression(i) } }, + Statements = new[] { TransformBlock(ilSwitch.CaseBlocks[i]) } + }, SwitchStatement.SwitchSectionRole); + } + yield return switchStmt; } else if (node is ILTryCatchBlock) { ILTryCatchBlock tryCatchNode = ((ILTryCatchBlock)node); List<Ast.CatchClause> catchClauses = new List<CatchClause>(); @@ -189,7 +205,7 @@ namespace Decompiler return args; } - object TransformExpression(ILExpression expr) + AstNode TransformExpression(ILExpression expr) { List<Ast.Expression> args = TransformExpressionArguments(expr); return TransformByteCode(methodDef, expr, args); @@ -252,7 +268,7 @@ namespace Decompiler */ } - static object TransformByteCode(MethodDefinition methodDef, ILExpression byteCode, List<Ast.Expression> args) + AstNode TransformByteCode(MethodDefinition methodDef, ILExpression byteCode, List<Ast.Expression> args) { try { AstNode ret = TransformByteCode_Internal(methodDef, byteCode, args); @@ -267,7 +283,7 @@ namespace Decompiler } } - static string FormatByteCodeOperand(object operand) + string FormatByteCodeOperand(object operand) { if (operand == null) { return string.Empty; @@ -292,7 +308,7 @@ namespace Decompiler } } - static AstNode TransformByteCode_Internal(MethodDefinition methodDef, ILExpression byteCode, List<Ast.Expression> args) + AstNode TransformByteCode_Internal(MethodDefinition methodDef, ILExpression byteCode, List<Ast.Expression> args) { // throw new NotImplementedException(); @@ -326,6 +342,7 @@ namespace Decompiler case Code.Sub_Ovf: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Subtract, arg2); case Code.Sub_Ovf_Un: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Subtract, arg2); case Code.And: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.BitwiseAnd, arg2); + case Code.Or: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.BitwiseOr, arg2); case Code.Xor: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.ExclusiveOr, arg2); case Code.Shl: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.ShiftLeft, arg2); case Code.Shr: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.ShiftRight, arg2); @@ -360,7 +377,7 @@ namespace Decompiler case Code.Ldelem_Any: throw new NotImplementedException(); case Code.Ldelema: - return arg1.Indexer(arg2); + return MakeRef(arg1.Indexer(arg2)); case Code.Stelem_I: case Code.Stelem_I1: @@ -459,49 +476,28 @@ namespace Decompiler case Code.Box: throw new NotImplementedException(); case Code.Break: throw new NotImplementedException(); case Code.Call: + return TransformCall(false, operand, methodDef, args); case Code.Callvirt: - // TODO: Diferentiate vitual and non-vitual dispach - Cecil.MethodReference cecilMethod = ((MethodReference)operand); - Ast.Expression target; - List<Ast.Expression> methodArgs = new List<Ast.Expression>(args); - if (cecilMethod.HasThis) { - target = methodArgs[0]; - methodArgs.RemoveAt(0); - } else { - target = new TypeReferenceExpression { Type = AstBuilder.ConvertType(cecilMethod.DeclaringType)}; + return TransformCall(true, operand, methodDef, args); + case Code.Ldftn: + { + Cecil.MethodReference cecilMethod = ((MethodReference)operand); + var expr = new Ast.IdentifierExpression(cecilMethod.Name); + expr.TypeArguments = ConvertTypeArguments(cecilMethod); + expr.AddAnnotation(cecilMethod); + return new IdentifierExpression("ldftn").Invoke(expr) + .WithAnnotation(new Transforms.DelegateConstruction.Annotation(false, methodDef.DeclaringType)); } - - // TODO: Constructors are ignored - if (cecilMethod.Name == ".ctor") { - return new CommentStatement("Constructor"); + case Code.Ldvirtftn: + { + Cecil.MethodReference cecilMethod = ((MethodReference)operand); + var expr = new Ast.IdentifierExpression(cecilMethod.Name); + expr.TypeArguments = ConvertTypeArguments(cecilMethod); + expr.AddAnnotation(cecilMethod); + return new IdentifierExpression("ldvirtftn").Invoke(expr) + .WithAnnotation(new Transforms.DelegateConstruction.Annotation(true, methodDef.DeclaringType)); } - // TODO: Hack, detect properties properly - if (cecilMethod.Name.StartsWith("get_")) { - return target.Member(cecilMethod.Name.Remove(0, 4)).WithAnnotation(cecilMethod); - } else if (cecilMethod.Name.StartsWith("set_")) { - return new Ast.AssignmentExpression( - target.Member(cecilMethod.Name.Remove(0, 4)).WithAnnotation(cecilMethod), - methodArgs[0] - ); - } - - // Multi-dimensional array acces // TODO: do properly - /* - if (cecilMethod.Name == "Get") { - return new Ast.IndexerExpression(target, methodArgs); - } else if (cecilMethod.Name == "Set") { - Expression val = methodArgs[methodArgs.Count - 1]; - methodArgs.RemoveAt(methodArgs.Count - 1); - return new Ast.AssignmentExpression( - new Ast.IndexerExpression(target, methodArgs), - AssignmentOperatorType.Assign, - Convert(val, ((Cecil.ArrayType)((target.UserData as Dictionary<string, object>)["Type"])).ElementType) - ); - }*/ - - // Default invocation - return target.Invoke(cecilMethod.Name, methodArgs).WithAnnotation(cecilMethod); case Code.Calli: throw new NotImplementedException(); case Code.Castclass: return arg1.CastTo(operandAsTypeRef); case Code.Ckfinite: throw new NotImplementedException(); @@ -521,7 +517,12 @@ namespace Decompiler } else { return new Ast.IdentifierExpression(((ParameterDefinition)operand).Name); } - case Code.Ldarga: throw new NotImplementedException(); + case Code.Ldarga: + if (methodDef.HasThis && ((ParameterDefinition)operand).Index < 0) { + return MakeRef(new Ast.ThisReferenceExpression()); + } else { + return MakeRef(new Ast.IdentifierExpression(((ParameterDefinition)operand).Name)); + } case Code.Ldc_I4: case Code.Ldc_I8: case Code.Ldc_R4: @@ -540,16 +541,24 @@ namespace Decompiler .Member(((FieldReference)operand).Name).WithAnnotation(operand), arg1); case Code.Ldflda: - case Code.Ldsflda: throw new NotImplementedException(); - case Code.Ldftn: throw new NotImplementedException(); + return MakeRef(arg1.Member(((FieldReference) operand).Name).WithAnnotation(operand)); + case Code.Ldsflda: + return MakeRef( + AstBuilder.ConvertType(((FieldReference)operand).DeclaringType) + .Member(((FieldReference)operand).Name).WithAnnotation(operand)); case Code.Ldloc: if (operand is ILVariable) { return new Ast.IdentifierExpression(((ILVariable)operand).Name); } else { return new Ast.IdentifierExpression(((VariableDefinition)operand).Name); } - case Code.Ldloca: throw new NotImplementedException(); - case Code.Ldnull: return new Ast.PrimitiveExpression(null); + case Code.Ldloca: + if (operand is ILVariable) { + return MakeRef(new Ast.IdentifierExpression(((ILVariable)operand).Name)); + } else { + return MakeRef(new Ast.IdentifierExpression(((VariableDefinition)operand).Name)); + } + case Code.Ldnull: return new Ast.NullReferenceExpression(); case Code.Ldobj: throw new NotImplementedException(); case Code.Ldstr: return new Ast.PrimitiveExpression(operand); case Code.Ldtoken: @@ -558,7 +567,6 @@ namespace Decompiler } else { throw new NotImplementedException(); } - case Code.Ldvirtftn: throw new NotImplementedException(); case Code.Leave: return null; case Code.Localloc: throw new NotImplementedException(); case Code.Mkrefany: throw new NotImplementedException(); @@ -577,7 +585,6 @@ namespace Decompiler }; case Code.No: throw new NotImplementedException(); case Code.Nop: return null; - case Code.Or: throw new NotImplementedException(); case Code.Pop: return arg1; case Code.Readonly: throw new NotImplementedException(); case Code.Refanytype: throw new NotImplementedException(); @@ -591,32 +598,18 @@ namespace Decompiler } } case Code.Rethrow: return new Ast.ThrowStatement(); - case Code.Sizeof: throw new NotImplementedException(); + case Code.Sizeof: return new Ast.SizeOfExpression { Type = AstBuilder.ConvertType(operand as TypeReference) }; case Code.Starg: throw new NotImplementedException(); case Code.Stloc: { - if (operand is ILVariable) { - var astLocalVar = new Ast.VariableDeclarationStatement(); - astLocalVar.Type = new Ast.PrimitiveType("var"); - astLocalVar.Variables = new [] { - new Ast.VariableInitializer(((ILVariable)operand).Name, arg1) + ILVariable locVar = (ILVariable)operand; + if (!definedLocalVars.Contains(locVar)) { + definedLocalVars.Add(locVar); + return new Ast.VariableDeclarationStatement() { + Type = locVar.Type != null ? AstBuilder.ConvertType(locVar.Type) : new Ast.PrimitiveType("var"), + Variables = new[] { new Ast.VariableInitializer(locVar.Name, arg1) } }; - return astLocalVar; - } - VariableDefinition locVar = (VariableDefinition)operand; - string name = locVar.Name; - arg1 = Convert(arg1, locVar.VariableType); - if (localVarDefined.ContainsKey(name)) { - if (localVarDefined[name]) { - return new Ast.AssignmentExpression(new Ast.IdentifierExpression(name), arg1); - } else { - var astLocalVar = new Ast.VariableDeclarationStatement(); - astLocalVar.Type = AstBuilder.ConvertType(localVarTypes[name]); - astLocalVar.Variables = new[] { new Ast.VariableInitializer(name, arg1) }; - localVarDefined[name] = true; - return astLocalVar; - } } else { - return new Ast.AssignmentExpression(new Ast.IdentifierExpression(name), arg1); + return new Ast.AssignmentExpression(new Ast.IdentifierExpression(locVar.Name), arg1); } } case Code.Stobj: throw new NotImplementedException(); @@ -631,6 +624,84 @@ namespace Decompiler } } + static AstNode TransformCall(bool isVirtual, object operand, MethodDefinition methodDef, List<Ast.Expression> args) + { + Cecil.MethodReference cecilMethod = ((MethodReference)operand); + Ast.Expression target; + List<Ast.Expression> methodArgs = new List<Ast.Expression>(args); + if (cecilMethod.HasThis) { + target = methodArgs[0]; + methodArgs.RemoveAt(0); + + // Unpack any DirectionExpression that is used as target for the call + // (calling methods on value types implicitly passes the first argument by reference) + if (target is DirectionExpression) { + target = ((DirectionExpression)target).Expression; + target.Remove(); // detach from DirectionExpression + } + } else { + target = new TypeReferenceExpression { Type = AstBuilder.ConvertType(cecilMethod.DeclaringType) }; + } + if (target is ThisReferenceExpression && !isVirtual) { + // a non-virtual call on "this" might be a "base"-call. + if (cecilMethod.DeclaringType != methodDef.DeclaringType) { + // If we're not calling a method in the current class; we must be calling one in the base class. + target = new BaseReferenceExpression(); + } + } + + // Resolve the method to figure out whether it is an accessor: + Cecil.MethodDefinition cecilMethodDef = cecilMethod.Resolve(); + if (cecilMethodDef != null) { + if (cecilMethodDef.IsGetter && methodArgs.Count == 0) { + foreach (var prop in cecilMethodDef.DeclaringType.Properties) { + if (prop.GetMethod == cecilMethodDef) + return target.Member(prop.Name).WithAnnotation(prop); + } + } else if (cecilMethodDef.IsSetter && methodArgs.Count == 1) { + foreach (var prop in cecilMethodDef.DeclaringType.Properties) { + if (prop.SetMethod == cecilMethodDef) + return new Ast.AssignmentExpression(target.Member(prop.Name).WithAnnotation(prop), methodArgs[0]); + } + } else if (cecilMethodDef.IsAddOn && methodArgs.Count == 1) { + foreach (var ev in cecilMethodDef.DeclaringType.Events) { + if (ev.AddMethod == cecilMethodDef) { + return new Ast.AssignmentExpression { + Left = target.Member(ev.Name).WithAnnotation(ev), + Operator = AssignmentOperatorType.Add, + Right = methodArgs[0] + }; + } + } + } else if (cecilMethodDef.IsRemoveOn && methodArgs.Count == 1) { + foreach (var ev in cecilMethodDef.DeclaringType.Events) { + if (ev.RemoveMethod == cecilMethodDef) { + return new Ast.AssignmentExpression { + Left = target.Member(ev.Name).WithAnnotation(ev), + Operator = AssignmentOperatorType.Subtract, + Right = methodArgs[0] + }; + } + } + } + } + // Default invocation + return target.Invoke(cecilMethod.Name, ConvertTypeArguments(cecilMethod), methodArgs).WithAnnotation(cecilMethod); + } + + static IEnumerable<AstType> ConvertTypeArguments(MethodReference cecilMethod) + { + GenericInstanceMethod g = cecilMethod as GenericInstanceMethod; + if (g == null) + return null; + return g.GenericArguments.Select(t => AstBuilder.ConvertType(t)); + } + + static Ast.DirectionExpression MakeRef(Ast.Expression expr) + { + return new DirectionExpression { Expression = expr, FieldDirection = FieldDirection.Ref }; + } + static Ast.Expression Convert(Ast.Expression expr, Cecil.TypeReference reqType) { if (reqType == null) { diff --git a/ICSharpCode.Decompiler/Ast/NRefactoryExtensions.cs b/ICSharpCode.Decompiler/Ast/NRefactoryExtensions.cs index 729caba28..f7fe21949 100644 --- a/ICSharpCode.Decompiler/Ast/NRefactoryExtensions.cs +++ b/ICSharpCode.Decompiler/Ast/NRefactoryExtensions.cs @@ -10,7 +10,8 @@ namespace Decompiler { public static T WithAnnotation<T>(this T node, object annotation) where T : AstNode { - node.AddAnnotation(annotation); + if (annotation != null) + node.AddAnnotation(annotation); return node; } } diff --git a/ICSharpCode.Decompiler/Ast/Transforms/ConvertConstructorCallIntoInitializer.cs b/ICSharpCode.Decompiler/Ast/Transforms/ConvertConstructorCallIntoInitializer.cs new file mode 100644 index 000000000..36f6d4eb5 --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/Transforms/ConvertConstructorCallIntoInitializer.cs @@ -0,0 +1,42 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under MIT X11 license (for details please see \doc\license.txt) + +using System; +using System.Linq; +using ICSharpCode.NRefactory.CSharp; +using Mono.Cecil; + +namespace Decompiler.Transforms +{ + public class ConvertConstructorCallIntoInitializer : DepthFirstAstVisitor<object, object> + { + public override object VisitConstructorDeclaration(ConstructorDeclaration constructorDeclaration, object data) + { + ExpressionStatement stmt = constructorDeclaration.Body.Statements.FirstOrDefault() as ExpressionStatement; + if (stmt == null) + return null; + InvocationExpression invocation = stmt.Expression as InvocationExpression; + if (invocation == null) + return null; + MemberReferenceExpression mre = invocation.Target as MemberReferenceExpression; + if (mre != null && mre.MemberName == ".ctor") { + ConstructorInitializer ci = new ConstructorInitializer(); + if (mre.Target is ThisReferenceExpression) + ci.ConstructorInitializerType = ConstructorInitializerType.This; + else if (mre.Target is BaseReferenceExpression) + ci.ConstructorInitializerType = ConstructorInitializerType.Base; + else + return null; + // Move arguments from invocation to initializer: + var arguments = invocation.Arguments.ToArray(); + invocation.Arguments = null; + ci.Arguments = arguments; + // Add the initializer: + constructorDeclaration.Initializer = ci.WithAnnotation(invocation.Annotation<MethodReference>()); + // Remove the statement: + stmt.Remove(); + } + return null; + } + } +} diff --git a/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs b/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs new file mode 100644 index 000000000..4f3786120 --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs @@ -0,0 +1,130 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under MIT X11 license (for details please see \doc\license.txt) + +using System; +using System.Linq; +using ICSharpCode.Decompiler; +using ICSharpCode.NRefactory.CSharp; +using Mono.Cecil; + +namespace Decompiler.Transforms +{ + /// <summary> + /// Converts "new Action(obj, ldftn(func))" into "new Action(obj.func)". + /// For anonymous methods, creates an AnonymousMethodExpression. + /// </summary> + public class DelegateConstruction : DepthFirstAstVisitor<object, object> + { + internal sealed class Annotation + { + /// <summary> + /// ldftn or ldvirtftn? + /// </summary> + public readonly bool IsVirtual; + + /// <summary> + /// The method being decompiled. + /// </summary> + public readonly TypeDefinition ContainingType; + + public Annotation(bool isVirtual, TypeDefinition containingType) + { + this.IsVirtual = isVirtual; + this.ContainingType = containingType; + } + } + + public override object VisitObjectCreateExpression(ObjectCreateExpression objectCreateExpression, object data) + { + if (objectCreateExpression.Arguments.Count() == 2) { + Expression obj = objectCreateExpression.Arguments.First(); + Expression func = objectCreateExpression.Arguments.Last(); + Annotation annotation = func.Annotation<Annotation>(); + if (annotation != null) { + IdentifierExpression methodIdent = (IdentifierExpression)((InvocationExpression)func).Arguments.Single(); + MethodReference method = methodIdent.Annotation<MethodReference>(); + if (method != null) { + if (HandleAnonymousMethod(objectCreateExpression, obj, method, annotation.ContainingType)) + return null; + // Perform the transformation to "new Action(obj.func)". + obj.Remove(); + methodIdent.Remove(); + if (!annotation.IsVirtual && obj is ThisReferenceExpression) { + // maybe it's getting the pointer of a base method? + if (method.DeclaringType != annotation.ContainingType) { + obj = new BaseReferenceExpression(); + } + } + if (!annotation.IsVirtual && obj is NullReferenceExpression && !method.HasThis) { + // We're loading a static method. + // However it is possible to load extension methods with an instance, so we compare the number of arguments: + bool isExtensionMethod = false; + TypeReference delegateType = objectCreateExpression.Type.Annotation<TypeReference>(); + if (delegateType != null) { + TypeDefinition delegateTypeDef = delegateType.Resolve(); + if (delegateTypeDef != null) { + MethodDefinition invokeMethod = delegateTypeDef.Methods.FirstOrDefault(m => m.Name == "Invoke"); + if (invokeMethod != null) { + isExtensionMethod = (invokeMethod.Parameters.Count + 1 == method.Parameters.Count); + } + } + } + if (!isExtensionMethod) { + obj = new TypeReferenceExpression { Type = AstBuilder.ConvertType(method.DeclaringType) }; + } + } + // now transform the identifier into a member reference + MemberReferenceExpression mre = new MemberReferenceExpression { + Target = obj, + MemberName = methodIdent.Identifier, + TypeArguments = methodIdent.TypeArguments + }; + mre.AddAnnotation(method); + objectCreateExpression.Arguments = new [] { mre }; + return null; + } + } + } + return base.VisitObjectCreateExpression(objectCreateExpression, data); + } + + bool HandleAnonymousMethod(ObjectCreateExpression objectCreateExpression, Expression target, MethodReference methodRef, TypeDefinition containingType) + { + // Anonymous methods are defined in the same assembly, so there's no need to Resolve(). + MethodDefinition method = methodRef as MethodDefinition; + if (method == null || !method.Name.StartsWith("<", StringComparison.Ordinal)) + return false; + if (!(method.IsCompilerGenerated() || method.DeclaringType.IsCompilerGenerated())) + return false; + TypeDefinition methodContainingType = method.DeclaringType; + // check that methodContainingType is within containingType + while (methodContainingType != containingType) { + methodContainingType = methodContainingType.DeclaringType; + if (methodContainingType == null) + return false; + } + + // Decompile the anonymous method: + BlockStatement body = AstMethodBodyBuilder.CreateMethodBody(method); + TransformationPipeline.RunTransformationsUntil(body, v => v is DelegateConstruction); + body.AcceptVisitor(this, null); + + AnonymousMethodExpression ame = new AnonymousMethodExpression(); + if (method.Parameters.All(p => string.IsNullOrEmpty(p.Name))) { + ame.HasParameterList = false; + } else { + ame.HasParameterList = true; + ame.Parameters = AstBuilder.MakeParameters(method.Parameters); + } + ame.Body = body; + // Replace all occurrences of 'this' in the method body with the delegate's target: + foreach (AstNode node in body.Descendants) { + if (node is ThisReferenceExpression) + node.ReplaceWith(target.Clone()); + + } + objectCreateExpression.ReplaceWith(ame); + return true; + } + } +} diff --git a/ICSharpCode.Decompiler/Ast/Transforms/RemoveDeadLabels.cs b/ICSharpCode.Decompiler/Ast/Transforms/RemoveDeadLabels.cs new file mode 100644 index 000000000..b8f65771a --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/Transforms/RemoveDeadLabels.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using ICSharpCode.NRefactory.CSharp; + +namespace Decompiler.Transforms.Ast +{ + public class RemoveDeadLabels : DepthFirstAstVisitor<object, object> + { + List<string> usedLabels = new List<string>(); + 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/RemoveGotos.cs b/ICSharpCode.Decompiler/Ast/Transforms/RemoveGotos.cs index 0719e8f65..d45f5d121 100644 --- a/ICSharpCode.Decompiler/Ast/Transforms/RemoveGotos.cs +++ b/ICSharpCode.Decompiler/Ast/Transforms/RemoveGotos.cs @@ -1,17 +1,15 @@ using System; using System.Collections.Generic; using System.Linq; - using ICSharpCode.NRefactory.CSharp; namespace Decompiler.Transforms.Ast { - /* public class RemoveGotos: DepthFirstAstVisitor<object, object> { - Stack<Statement> enteredLoops = new Stack<Statement>(); + Stack<ForStatement> enteredLoops = new Stack<ForStatement>(); - Statement CurrentLoop { + ForStatement CurrentLoop { get { if (enteredLoops.Count > 0) { return enteredLoops.Peek(); @@ -29,32 +27,25 @@ namespace Decompiler.Transforms.Ast return null; } - public override object VisitWhileStatement(WhileStatement whileStatement, object data) - { - enteredLoops.Push(whileStatement); - base.VisitWhileStatement(whileStatement, 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 - INode lastStmt = blockStatement.Children.Last(); - // End of while loop - if (lastStmt is ContinueStatement && - blockStatement.Parent is DoLoopStatement) - { - blockStatement.Children.Remove(lastStmt); - return null; - } + AstNode lastStmt = blockStatement.Children.LastOrDefault(); // End of for loop if (lastStmt is ContinueStatement && blockStatement.Parent is ForStatement) { - blockStatement.Children.Remove(lastStmt); + lastStmt.Remove(); return null; } // End of method @@ -62,7 +53,7 @@ namespace Decompiler.Transforms.Ast (blockStatement.Parent is MethodDeclaration || blockStatement.Parent is ConstructorDeclaration) && ((ReturnStatement)lastStmt).Expression.IsNull) { - blockStatement.Children.Remove(lastStmt); + lastStmt.Remove(); return null; } @@ -75,7 +66,7 @@ namespace Decompiler.Transforms.Ast { if (statement == null) throw new ArgumentNullException(); - Statement next = (Statement)statement.Next(); + Statement next = (Statement)statement.NextSibling; if (next != null) { return EnterBlockStatement(next); @@ -105,12 +96,12 @@ namespace Decompiler.Transforms.Ast // Iterator; Condition; Body if (statement is ForStatement) { ForStatement forLoop = statement as ForStatement; - if (forLoop.Iterator.Count > 0) { - return forLoop.Iterator[0]; + if (forLoop.Iterators.Any()) { + return forLoop.Iterators.First(); } else if (!forLoop.Condition.IsNull) { return forLoop.Condition; } else { - return EnterBlockStatement((Statement)forLoop.EmbeddedStatement.Children.First()); + return EnterBlockStatement((Statement)forLoop.EmbeddedStatement.FirstChild); } } @@ -125,14 +116,12 @@ namespace Decompiler.Transforms.Ast // For loop starts as follows: Initializers; Condition; Body if (statement is ForStatement) { ForStatement forLoop = statement as ForStatement; - if (forLoop.Initializers.Count > 0) { - return forLoop.Initializers[0]; + if (forLoop.Initializers.Any()) { + return forLoop.Initializers.First(); } else if (!forLoop.Condition.IsNull) { return forLoop.Condition; - } else if (forLoop.EmbeddedStatement is BlockStatement && - forLoop.EmbeddedStatement.Children.Count > 0) { - statement = (Statement)forLoop.EmbeddedStatement.Children.First(); - return EnterBlockStatement(statement); // Simplify again + } else if (forLoop.EmbeddedStatement.Children.FirstOrDefault() is Statement) { + return EnterBlockStatement((Statement)forLoop.EmbeddedStatement.FirstChild); // Simplify again } } @@ -142,11 +131,11 @@ namespace Decompiler.Transforms.Ast public override object VisitGotoStatement(GotoStatement gotoStatement, object data) { // Remove redundant goto which goes to a label that imideately follows - INode fallthoughTarget = GetNextStatement(gotoStatement); + AstNode fallthoughTarget = GetNextStatement(gotoStatement); while(true) { if (fallthoughTarget is LabelStatement) { if ((fallthoughTarget as LabelStatement).Label == gotoStatement.Label) { - RemoveCurrentNode(); + gotoStatement.Remove(); return null; } else { fallthoughTarget = GetNextStatement((LabelStatement)fallthoughTarget); @@ -159,10 +148,10 @@ namespace Decompiler.Transforms.Ast // Replace goto with 'break' // Break statement moves right outside the looop if (CurrentLoop != null) { - INode breakTarget = GetNextStatement(CurrentLoop); + AstNode breakTarget = GetNextStatement(CurrentLoop); if ((breakTarget is LabelStatement) && (breakTarget as LabelStatement).Label == gotoStatement.Label) { - ReplaceCurrentNode(new BreakStatement()); + gotoStatement.ReplaceWith(new BreakStatement()); return null; } } @@ -171,24 +160,24 @@ namespace Decompiler.Transforms.Ast // Continue statement which moves at the very end of loop if (CurrentLoop != null && (CurrentLoop.EmbeddedStatement is BlockStatement) && - ((CurrentLoop.EmbeddedStatement as BlockStatement).Children.Last() as LabelStatement) != null && - ((CurrentLoop.EmbeddedStatement as BlockStatement).Children.Last() as LabelStatement).Label == gotoStatement.Label) { - ReplaceCurrentNode(new ContinueStatement()); + ((CurrentLoop.EmbeddedStatement as BlockStatement).LastChild as LabelStatement) != null && + ((CurrentLoop.EmbeddedStatement as BlockStatement).LastChild 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) { - INode continueTarget = ExitBlockStatement(CurrentLoop); // The start of the loop + AstNode continueTarget = ExitBlockStatement(CurrentLoop); // The start of the loop if ((continueTarget is LabelStatement) && (continueTarget as LabelStatement).Label == gotoStatement.Label) { - ReplaceCurrentNode(new ContinueStatement()); + gotoStatement.ReplaceWith(new ContinueStatement()); return null; } } return null; } - }*/ + } } diff --git a/ICSharpCode.Decompiler/Ast/Transforms/Idioms.cs b/ICSharpCode.Decompiler/Ast/Transforms/ReplaceMethodCallsWithOperators.cs similarity index 87% rename from ICSharpCode.Decompiler/Ast/Transforms/Idioms.cs rename to ICSharpCode.Decompiler/Ast/Transforms/ReplaceMethodCallsWithOperators.cs index 32670ad8e..25215179f 100644 --- a/ICSharpCode.Decompiler/Ast/Transforms/Idioms.cs +++ b/ICSharpCode.Decompiler/Ast/Transforms/ReplaceMethodCallsWithOperators.cs @@ -5,9 +5,13 @@ using Mono.Cecil; using Ast = ICSharpCode.NRefactory.CSharp; using ICSharpCode.NRefactory.CSharp; -namespace Decompiler.Transforms.Ast +namespace Decompiler.Transforms { - public class Idioms: DepthFirstAstVisitor<object, object> + /// <summary> + /// Replaces method calls with the appropriate operator expressions. + /// Also simplifies "x = x op y" into "x op= y" where possible. + /// </summary> + public class ReplaceMethodCallsWithOperators : DepthFirstAstVisitor<object, object> { public override object VisitInvocationExpression(InvocationExpression invocationExpression, object data) { @@ -58,6 +62,19 @@ namespace Decompiler.Transforms.Ast ); return null; } + if (methodRef.Name == "op_Explicit" && arguments.Length == 1) { + arguments[0].Remove(); // detach argument + invocationExpression.ReplaceWith( + arguments[0].CastTo(AstBuilder.ConvertType(methodRef.ReturnType, methodRef.MethodReturnType)) + .WithAnnotation(methodRef) + ); + return null; + } + if (methodRef.Name == "op_Implicit" && arguments.Length == 1) { + arguments[0].Remove(); // detach argument + invocationExpression.ReplaceWith(arguments[0]); + return null; + } return null; } @@ -162,7 +179,7 @@ namespace Decompiler.Transforms.Ast break; } if (assignment.Operator != AssignmentOperatorType.Assign) { - // If we found a shorter operators, get rid of the BinaryOperatorExpression: + // If we found a shorter operator, get rid of the BinaryOperatorExpression: assignment.Right = binary.Right; } } @@ -174,18 +191,5 @@ namespace Decompiler.Transforms.Ast { return left is IdentifierExpression; // TODO } - - /* - public override object VisitCastExpression(CastExpression castExpression, object data) - { - MethodReference typeRef = invocationExpression.Annotation<TypeReference>(); - if (typeRef.FullName == Constants.Int32 && - castExpression.Expression is MemberReferenceExpression && - (castExpression.Expression as MemberReferenceExpression).MemberName == "Length") { - ReplaceCurrentNode(castExpression.Expression); - return null; - } - return base.VisitCastExpression(castExpression, data); - }*/ } } diff --git a/ICSharpCode.Decompiler/Ast/Transforms/RestoreLoop.cs b/ICSharpCode.Decompiler/Ast/Transforms/RestoreLoop.cs index ed3d91015..09f07d78e 100644 --- a/ICSharpCode.Decompiler/Ast/Transforms/RestoreLoop.cs +++ b/ICSharpCode.Decompiler/Ast/Transforms/RestoreLoop.cs @@ -1,10 +1,9 @@ using System; - +using System.Linq; using ICSharpCode.NRefactory.CSharp; namespace Decompiler.Transforms.Ast { - /* public class RestoreLoop: DepthFirstAstVisitor<object, object> { public override object VisitForStatement(ForStatement forStatement, object data) @@ -12,25 +11,25 @@ namespace Decompiler.Transforms.Ast base.VisitForStatement(forStatement, data); // Restore loop initializer - if (forStatement.Initializers.Count == 0) { - LocalVariableDeclaration varDeclr = forStatement.Previous() as LocalVariableDeclaration; + if (!forStatement.Initializers.Any()) { + VariableDeclarationStatement varDeclr = forStatement.PrevSibling as VariableDeclarationStatement; if (varDeclr != null) { varDeclr.ReplaceWith(Statement.Null); - forStatement.Initializers.Add(varDeclr); + forStatement.Initializers = new Statement[] { varDeclr }; } } // Restore loop condition if (forStatement.Condition.IsNull && - forStatement.EmbeddedStatement.Children.Count >= 3) + forStatement.EmbeddedStatement.Children.Count() >= 3) { - IfElseStatement condition = forStatement.EmbeddedStatement.Children[0] as IfElseStatement; - BreakStatement breakStmt = forStatement.EmbeddedStatement.Children[1] as BreakStatement; - LabelStatement label = forStatement.EmbeddedStatement.Children[2] as LabelStatement; + 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.Count == 1) + condition.TrueStatement.Children.Count() == 1) { - GotoStatement gotoStmt = condition.TrueStatement[0] as GotoStatement; + GotoStatement gotoStmt = condition.TrueStatement.FirstChild as GotoStatement; if (gotoStmt != null && gotoStmt.Label == label.Label) { condition.Remove(); breakStmt.Remove(); @@ -41,34 +40,34 @@ namespace Decompiler.Transforms.Ast // Restore loop condition (version 2) if (forStatement.Condition.IsNull) { - IfElseStatement condition = forStatement.EmbeddedStatement.Children.First() as IfElseStatement; + IfElseStatement condition = forStatement.EmbeddedStatement.FirstChild as IfElseStatement; if (condition != null && - condition.TrueStatement.Count == 1 && - condition.TrueStatement[0] is BlockStatement && - condition.TrueStatement[0].Children.Count == 1 && - condition.TrueStatement[0].Children.First() is BreakStatement && - condition.FalseStatement.Count == 1 && - condition.FalseStatement[0] is BlockStatement && - condition.FalseStatement[0].Children.Count == 0) + 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(condition.Condition, UnaryOperatorType.Not); + forStatement.Condition = new UnaryOperatorExpression() { Expression = condition.Condition, Operator = UnaryOperatorType.Not }; } } // Restore loop iterator - if (forStatement.EmbeddedStatement.Children.Count > 0 && - forStatement.Iterator.Count == 0) + if (forStatement.EmbeddedStatement.Children.Any() && + !forStatement.Iterators.Any()) { - ExpressionStatement lastStmt = forStatement.EmbeddedStatement.Children.Last() as ExpressionStatement; + ExpressionStatement lastStmt = forStatement.EmbeddedStatement.LastChild as ExpressionStatement; if (lastStmt != null && (lastStmt.Expression is AssignmentExpression || lastStmt.Expression is UnaryOperatorExpression)) { lastStmt.Remove(); - forStatement.Iterator.Add(lastStmt); + forStatement.Iterators = new Statement[] { lastStmt }; } } return null; } - }*/ + } } diff --git a/ICSharpCode.Decompiler/Ast/Transforms/SimplifyTypeReferences.cs b/ICSharpCode.Decompiler/Ast/Transforms/SimplifyTypeReferences.cs deleted file mode 100644 index c179ffdac..000000000 --- a/ICSharpCode.Decompiler/Ast/Transforms/SimplifyTypeReferences.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; - -using ICSharpCode.NRefactory.CSharp; - -namespace Decompiler.Transforms.Ast -{ - public class SimplifyTypeReferences: DepthFirstAstVisitor<object, object> - { - string currentNamepace = string.Empty; - string currentClass = null; - - public override object VisitNamespaceDeclaration(NamespaceDeclaration namespaceDeclaration, object data) - { - currentNamepace = namespaceDeclaration.Name; - base.VisitNamespaceDeclaration(namespaceDeclaration, data); - currentNamepace = string.Empty; - return null; - } - - public override object VisitTypeDeclaration(TypeDeclaration typeDeclaration, object data) - { - currentClass = currentNamepace + "." + typeDeclaration.Name; - base.VisitTypeDeclaration(typeDeclaration, data); - currentClass = null; - return null; - } - } -} diff --git a/ICSharpCode.Decompiler/Ast/Transforms/TransformationPipeline.cs b/ICSharpCode.Decompiler/Ast/Transforms/TransformationPipeline.cs new file mode 100644 index 000000000..8c2d5b235 --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/Transforms/TransformationPipeline.cs @@ -0,0 +1,50 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under MIT X11 license (for details please see \doc\license.txt) + +using System; +using ICSharpCode.NRefactory.CSharp; + +namespace Decompiler.Transforms +{ + public static class TransformationPipeline + { + public static IAstVisitor<object, object>[] CreatePipeline() + { + return new IAstVisitor<object, object>[] { + new DelegateConstruction(), + new ConvertConstructorCallIntoInitializer(), + new ReplaceMethodCallsWithOperators() + }; + } + + public static void RunTransformations(AstNode node) + { + RunTransformationsUntil(node, null); + } + + public static void RunTransformationsUntil(AstNode node, Predicate<IAstVisitor<object, object>> abortCondition) + { + if (node == null) + return; + for (int i = 0; i < 4; i++) { + 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); + node.AcceptVisitor(new Transforms.Ast.PushNegation(), null); + } + } + + foreach (var visitor in CreatePipeline()) { + if (abortCondition != null && abortCondition(visitor)) + return; + node.AcceptVisitor(visitor, null); + } + } + } +} diff --git a/ICSharpCode.Decompiler/CecilExtensions.cs b/ICSharpCode.Decompiler/CecilExtensions.cs index 4c17373ec..a97ed2427 100644 --- a/ICSharpCode.Decompiler/CecilExtensions.cs +++ b/ICSharpCode.Decompiler/CecilExtensions.cs @@ -158,5 +158,16 @@ namespace ICSharpCode.Decompiler } return accessorMethods; } + + public static bool IsCompilerGenerated(this ICustomAttributeProvider provider) + { + if (provider.HasCustomAttributes) { + foreach (CustomAttribute a in provider.CustomAttributes) { + if (a.AttributeType.FullName == "System.Runtime.CompilerServices.CompilerGeneratedAttribute") + return true; + } + } + return false; + } } } diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index a45af9653..77a18ab6b 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -50,16 +50,19 @@ </ItemGroup> <ItemGroup> <Compile Include="Ast\AstBuilder.cs" /> - <Compile Include="Ast\AstMetodBodyBuilder.cs" /> + <Compile Include="Ast\AstMethodBodyBuilder.cs" /> <Compile Include="Ast\CommentStatement.cs" /> <Compile Include="Ast\NRefactoryExtensions.cs" /> <Compile Include="Ast\TextOutputFormatter.cs" /> - <Compile Include="Ast\Transforms\Idioms.cs" /> + <Compile Include="Ast\Transforms\ConvertConstructorCallIntoInitializer.cs" /> + <Compile Include="Ast\Transforms\DelegateConstruction.cs" /> + <Compile Include="Ast\Transforms\ReplaceMethodCallsWithOperators.cs" /> <Compile Include="Ast\Transforms\PushNegation.cs" /> + <Compile Include="Ast\Transforms\RemoveDeadLabels.cs" /> <Compile Include="Ast\Transforms\RemoveEmptyElseBody.cs" /> <Compile Include="Ast\Transforms\RemoveGotos.cs" /> <Compile Include="Ast\Transforms\RestoreLoop.cs" /> - <Compile Include="Ast\Transforms\SimplifyTypeReferences.cs" /> + <Compile Include="Ast\Transforms\TransformationPipeline.cs" /> <Compile Include="CecilExtensions.cs" /> <Compile Include="Disassembler\CodeMappings.cs" /> <Compile Include="Disassembler\DisassemblerHelpers.cs" /> diff --git a/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs b/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs index 9c9b768f8..e520437f0 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs @@ -32,6 +32,7 @@ namespace Decompiler { public ILLabel Label; // Non-null only if needed public int Offset; + public int EndOffset; public OpCode OpCode; public object Operand; public int? PopCount; // Null means pop all @@ -94,6 +95,8 @@ namespace Decompiler Dictionary<Instruction, ByteCode> instrToByteCode = new Dictionary<Instruction, ByteCode>(); Dictionary<ILVariable, bool> allowInline = new Dictionary<ILVariable, bool>(); + public List<ILVariable> Variables; + public List<ILNode> Build(MethodDefinition methodDef, bool optimize) { this.methodDef = methodDef; @@ -118,6 +121,7 @@ namespace Decompiler MethodBodyRocks.ExpandMacro(ref opCode, ref operand, methodDef.Body); ByteCode byteCode = new ByteCode() { Offset = inst.Offset, + EndOffset = inst.Next != null ? inst.Next.Offset : methodDef.Body.CodeSize, OpCode = opCode, Operand = operand, PopCount = inst.GetPopCount(), @@ -247,27 +251,27 @@ namespace Decompiler } // Convert local varibles - List<ILVariable> vars = methodDef.Body.Variables.Select(v => new ILVariable() { Name = string.IsNullOrEmpty(v.Name) ? "var_" + v.Index : v.Name }).ToList(); - int[] numReads = new int[vars.Count]; - int[] numWrites = new int[vars.Count]; + Variables = methodDef.Body.Variables.Select(v => new ILVariable() { Name = string.IsNullOrEmpty(v.Name) ? "var_" + v.Index : v.Name, Type = v.VariableType }).ToList(); + int[] numReads = new int[Variables.Count]; + int[] numWrites = new int[Variables.Count]; foreach(ByteCode byteCode in body) { if (byteCode.OpCode == OpCodes.Ldloc) { int index = ((VariableDefinition)byteCode.Operand).Index; - byteCode.Operand = vars[index]; + byteCode.Operand = Variables[index]; numReads[index]++; } if (byteCode.OpCode == OpCodes.Stloc) { int index = ((VariableDefinition)byteCode.Operand).Index; - byteCode.Operand = vars[index]; + byteCode.Operand = Variables[index]; numWrites[index]++; } } // Find which variables we can inline if (this.optimize) { - for (int i = 0; i < vars.Count; i++) { + for (int i = 0; i < Variables.Count; i++) { if (numReads[i] == 1 && numWrites[i] == 1) { - allowInline[vars[i]] = true; + allowInline[Variables[i]] = true; } } } @@ -360,6 +364,7 @@ namespace Decompiler MethodBodyRocks.ExpandMacro(ref opCode, ref operand, methodDef.Body); ILExpression expr = new ILExpression(opCode, operand); + expr.ILRanges.Add(new ILRange() { From = byteCode.Offset, To = byteCode.EndOffset }); // Label for this instruction if (byteCode.Label != null) { @@ -417,6 +422,9 @@ namespace Decompiler bool canInline; allowInline.TryGetValue((ILVariable)arg.Operand, out canInline); if (arg.Operand == currExpr.Operand && canInline) { + // Assigne the ranges for optimized away instrustions somewhere + currExpr.Arguments[0].ILRanges.AddRange(currExpr.ILRanges); + currExpr.Arguments[0].ILRanges.AddRange(nextExpr.Arguments[j].ILRanges); ast.RemoveAt(i); nextExpr.Arguments[j] = currExpr.Arguments[0]; // Inline the stloc body i -= 2; // Try the same index again diff --git a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs index c3e3e7e1b..18cc956a0 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs @@ -14,25 +14,27 @@ namespace Decompiler.ControlFlow public void Optimize(ILBlock method) { - var blocks = method.GetSelfAndChildrenRecursive<ILBlock>().ToList(); + foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>().ToList()) { + SplitToMovableBlocks(block); + } - foreach(ILBlock block in blocks) { + foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>().Where(b => !(b is ILMoveableBlock)).ToList()) { ControlFlowGraph graph; - - SplitToMovableBlocks(block); - graph = BuildGraph(block.Body, block.EntryPoint); graph.ComputeDominance(); graph.ComputeDominanceFrontier(); - block.Body = FindLoops(new HashSet<ControlFlowNode>(graph.Nodes.Skip(3)), graph.EntryPoint); - + block.Body = FindLoops(new HashSet<ControlFlowNode>(graph.Nodes.Skip(3)), graph.EntryPoint, true); + } + + foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>().Where(b => !(b is ILMoveableBlock)).ToList()) { + ControlFlowGraph graph; graph = BuildGraph(block.Body, block.EntryPoint); graph.ComputeDominance(); graph.ComputeDominanceFrontier(); block.Body = FindConditions(new HashSet<ControlFlowNode>(graph.Nodes.Skip(3)), graph.EntryPoint); } - OrderNodes(method); + // OrderNodes(method); FlattenNestedMovableBlocks(method); SimpleGotoRemoval(method); RemoveDeadLabels(method); @@ -53,6 +55,7 @@ namespace Decompiler.ControlFlow void SplitToMovableBlocks(ILBlock block) { // Remve no-ops + // TODO: Assign the no-op range to someting block.Body = block.Body.Where(n => !(n is ILExpression && ((ILExpression)n).OpCode == OpCodes.Nop)).ToList(); List<ILNode> moveableBlocks = new List<ILNode>(); @@ -153,7 +156,7 @@ namespace Decompiler.ControlFlow return new ControlFlowGraph(cfNodes.ToArray()); } - static List<ILNode> FindLoops(HashSet<ControlFlowNode> nodes, ControlFlowNode entryPoint) + List<ILNode> FindLoops(HashSet<ControlFlowNode> nodes, ControlFlowNode entryPoint, bool excludeEntryPoint) { List<ILNode> result = new List<ILNode>(); @@ -164,14 +167,16 @@ namespace Decompiler.ControlFlow if (nodes.Contains(node) && node.DominanceFrontier.Contains(node) - && node != entryPoint) + && (node != entryPoint || !excludeEntryPoint)) { HashSet<ControlFlowNode> loopContents = new HashSet<ControlFlowNode>(); FindLoopContents(nodes, loopContents, node, node); // Move the content into loop block nodes.ExceptWith(loopContents); - result.Add(new ILLoop() { ContentBlock = new ILBlock(FindLoops(loopContents, node)) }); + ILLabel entryLabel = new ILLabel() { Name = "Loop_" + (nextBlockIndex++) }; + ((ILBlock)node.UserData).Body.Insert(0, entryLabel); + result.Add(new ILLoop() { ContentBlock = new ILBlock(FindLoops(loopContents, node, true)) { EntryPoint = entryLabel } }); } // Using the dominator tree should ensure we find the the widest loop first @@ -206,58 +211,118 @@ namespace Decompiler.ControlFlow while(agenda.Count > 0) { ControlFlowNode node = agenda.Dequeue(); - ILMoveableBlock block = node.UserData as ILMoveableBlock; - // Find a block that represents a simple condition - if (nodes.Contains(node) && block != null && block.Body.Count == 3) { + if (nodes.Contains(node)) { - ILLabel label = block.Body[0] as ILLabel; - ILExpression condBranch = block.Body[1] as ILExpression; - ILExpression statBranch = block.Body[2] as ILExpression; + ILMoveableBlock block = node.UserData as ILMoveableBlock; - if (label != null && - condBranch != null && condBranch.Operand is ILLabel && condBranch.Arguments.Count > 0 && - statBranch != null && statBranch.Operand is ILLabel && statBranch.Arguments.Count == 0) - { - ControlFlowNode condTarget; - ControlFlowNode statTarget; - if (labelToCfNode.TryGetValue((ILLabel)condBranch.Operand, out condTarget) && - labelToCfNode.TryGetValue((ILLabel)statBranch.Operand, out statTarget)) + if (block != null && block.Body.Count == 3) { + + ILLabel label = block.Body[0] as ILLabel; + ILExpression condBranch = block.Body[1] as ILExpression; + ILExpression statBranch = block.Body[2] as ILExpression; + + // Switch + if (label != null && + condBranch != null && condBranch.Operand is ILLabel[] && condBranch.Arguments.Count > 0 && + statBranch != null && statBranch.Operand is ILLabel && statBranch.Arguments.Count == 0) + { + ILSwitch ilSwitch = new ILSwitch() { Condition = condBranch }; + + // Replace the two branches with a conditional structure - this preserves the node label + block.Body.Remove(condBranch); + block.Body.Remove(statBranch); + block.Body.Add(ilSwitch); + + ControlFlowNode statTarget = null; + labelToCfNode.TryGetValue((ILLabel)statBranch.Operand, out statTarget); + + // Pull in the conditional code + HashSet<ControlFlowNode> frontiers = new HashSet<ControlFlowNode>(); + + if (statTarget != null) + frontiers.UnionWith(statTarget.DominanceFrontier); + + foreach(ILLabel condLabel in (ILLabel[])condBranch.Operand) { + ControlFlowNode condTarget = null; + labelToCfNode.TryGetValue(condLabel, out condTarget); + + if (condTarget != null) + frontiers.UnionWith(condTarget.DominanceFrontier); + } + + foreach(ILLabel condLabel in (ILLabel[])condBranch.Operand) { + ControlFlowNode condTarget = null; + labelToCfNode.TryGetValue(condLabel, out condTarget); + + ILBlock caseBlock = new ILBlock() { EntryPoint = condLabel }; + if (condTarget != null && !frontiers.Contains(condTarget)) { + HashSet<ControlFlowNode> content = FindDominatedNodes(nodes, condTarget); + nodes.ExceptWith(content); + caseBlock.Body.AddRange(FindConditions(content, condTarget)); + } + ilSwitch.CaseBlocks.Add(caseBlock); + } + + // The labels will not be used - kill them + condBranch.Operand = null; + + result.Add(block); + nodes.Remove(node); + } + + // Two-way branch + if (label != null && + condBranch != null && condBranch.Operand is ILLabel && condBranch.Arguments.Count > 0 && + statBranch != null && statBranch.Operand is ILLabel && statBranch.Arguments.Count == 0) { + ControlFlowNode statTarget = null; + labelToCfNode.TryGetValue((ILLabel)statBranch.Operand, out statTarget); + ControlFlowNode condTarget = null; + labelToCfNode.TryGetValue((ILLabel)condBranch.Operand, out condTarget); + ILCondition condition = new ILCondition() { Condition = condBranch, TrueBlock = new ILBlock() { EntryPoint = (ILLabel)condBranch.Operand }, FalseBlock = new ILBlock() { EntryPoint = (ILLabel)statBranch.Operand } }; - // The label will not be used - kill it - condBranch.Operand = null; - - // Replace the two branches with a conditional structure + // Replace the two branches with a conditional structure - this preserves the node label block.Body.Remove(condBranch); block.Body.Remove(statBranch); block.Body.Add(condition); - result.Add(block); // Pull in the conditional code HashSet<ControlFlowNode> frontiers = new HashSet<ControlFlowNode>(); - frontiers.UnionWith(condTarget.DominanceFrontier); - frontiers.UnionWith(statTarget.DominanceFrontier); + if (statTarget != null) + frontiers.UnionWith(statTarget.DominanceFrontier); + if (condTarget != null) + frontiers.UnionWith(condTarget.DominanceFrontier); - if (!frontiers.Contains(condTarget)) { + if (condTarget != null && !frontiers.Contains(condTarget)) { HashSet<ControlFlowNode> content = FindDominatedNodes(nodes, condTarget); nodes.ExceptWith(content); condition.TrueBlock.Body.AddRange(FindConditions(content, condTarget)); } - if (!frontiers.Contains(statTarget)) { + if (statTarget != null && !frontiers.Contains(statTarget)) { HashSet<ControlFlowNode> content = FindDominatedNodes(nodes, statTarget); nodes.ExceptWith(content); condition.FalseBlock.Body.AddRange(FindConditions(content, statTarget)); } + // The label will not be used - kill it + condBranch.Operand = null; + + result.Add(block); nodes.Remove(node); } } + + // Add the node now so that we have good ordering + if (nodes.Contains(node)) { + result.Add((ILNode)node.UserData); + nodes.Remove(node); + } } // Using the dominator tree should ensure we find the the widest loop first @@ -290,7 +355,8 @@ namespace Decompiler.ControlFlow } } } - result.Add(head); + if (nodes.Contains(head)) + result.Add(head); return result; } @@ -404,6 +470,7 @@ namespace Decompiler.ControlFlow void SimpleGotoRemoval(ILBlock ast) { + // TODO: Assign IL ranges from br to something else var blocks = ast.GetSelfAndChildrenRecursive<ILBlock>().ToList(); foreach(ILBlock block in blocks) { for (int i = 0; i < block.Body.Count; i++) { diff --git a/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs b/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs index 3601c794b..61756612f 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; - using Decompiler.ControlFlow; using Mono.Cecil; using Mono.Cecil.Cil; @@ -44,10 +44,16 @@ namespace Decompiler { yield break; } + + public override string ToString() + { + return this.GetType().Name; + } } public class ILBlock: ILNode { + // TODO: This should really be a goto, not a label public ILLabel EntryPoint; public List<ILNode> Body; @@ -101,17 +107,13 @@ namespace Decompiler if (this.FinallyBlock != null) yield return this.FinallyBlock; } - - public override string ToString() - { - return "Try-Catch{}"; - } } public class ILVariable { public string Name; public bool IsGenerated; + public TypeReference Type; public override string ToString() { @@ -119,17 +121,31 @@ namespace Decompiler } } + public class ILRange + { + public int From; + public int To; // Exlusive + + public override string ToString() + { + return string.Format("{0}-{1}", From, To); + } + } + public class ILExpression: ILNode { public OpCode OpCode { get; set; } public object Operand { get; set; } public List<ILExpression> Arguments { get; set; } + // Mapping to the original instructions (useful for debugging) + public List<ILRange> ILRanges { get; set; } public ILExpression(OpCode opCode, object operand, params ILExpression[] args) { this.OpCode = opCode; this.Operand = operand; this.Arguments = new List<ILExpression>(args); + this.ILRanges = new List<ILRange>(1); } public IEnumerable<ILLabel> GetBranchTargets() @@ -143,6 +159,27 @@ namespace Decompiler } } + public List<ILRange> GetILRanges() + { + List<ILRange> ranges = new List<ILRange>(); + foreach(ILExpression expr in this.GetSelfAndChildrenRecursive<ILExpression>()) { + ranges.AddRange(expr.ILRanges); + } + ranges = ranges.OrderBy(r => r.From).ToList(); + for (int i = 0; i < ranges.Count - 1;) { + ILRange curr = ranges[i]; + ILRange next = ranges[i + 1]; + // Merge consequtive ranges if they intersect + if (curr.From <= next.From && next.From <= curr.To) { + curr.To = Math.Max(curr.To, next.To); + ranges.RemoveAt(i + 1); + } else { + i++; + } + } + return ranges; + } + public override IEnumerable<ILNode> GetChildren() { return Arguments; @@ -191,4 +228,18 @@ namespace Decompiler yield return FalseBlock; } } + + public class ILSwitch: ILNode + { + public ILExpression Condition; + public List<ILBlock> CaseBlocks = new List<ILBlock>(); + + public override IEnumerable<ILNode> GetChildren() + { + yield return Condition; + foreach (ILBlock caseBlock in this.CaseBlocks) { + yield return caseBlock; + } + } + } } diff --git a/ILSpy/CSharpLanguage.cs b/ILSpy/CSharpLanguage.cs index 5fdf8ea58..3368f1594 100644 --- a/ILSpy/CSharpLanguage.cs +++ b/ILSpy/CSharpLanguage.cs @@ -17,9 +17,11 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Collections.Generic; using System.IO; using System.Linq; using Decompiler; +using Decompiler.Transforms; using ICSharpCode.Decompiler; using ICSharpCode.NRefactory.CSharp; using Mono.Cecil; @@ -31,8 +33,33 @@ namespace ICSharpCode.ILSpy /// </summary> public class CSharpLanguage : Language { + string name = "C#"; + Predicate<IAstVisitor<object, object>> transformAbortCondition; + + public CSharpLanguage() + { + } + + #if DEBUG + internal static IEnumerable<CSharpLanguage> GetDebugLanguages() + { + string lastTransformName = "no transforms"; + foreach (Type _transformType in TransformationPipeline.CreatePipeline().Select(v => v.GetType()).Distinct()) { + Type transformType = _transformType; // copy for lambda + yield return new CSharpLanguage { + transformAbortCondition = v => transformType.IsInstanceOfType(v), + name = "C# - " + lastTransformName + }; + lastTransformName = "after " + transformType.Name; + } + yield return new CSharpLanguage { + name = "C# - " + lastTransformName + }; + } + #endif + public override string Name { - get { return "C#"; } + get { return name; } } public override string FileExtension { @@ -43,35 +70,35 @@ namespace ICSharpCode.ILSpy { AstBuilder codeDomBuilder = new AstBuilder(); codeDomBuilder.AddMethod(method); - codeDomBuilder.GenerateCode(output); + codeDomBuilder.GenerateCode(output, transformAbortCondition); } public override void DecompileProperty(PropertyDefinition property, ITextOutput output, DecompilationOptions options) { AstBuilder codeDomBuilder = new AstBuilder(); codeDomBuilder.AddProperty(property); - codeDomBuilder.GenerateCode(output); + codeDomBuilder.GenerateCode(output, transformAbortCondition); } public override void DecompileField(FieldDefinition field, ITextOutput output, DecompilationOptions options) { AstBuilder codeDomBuilder = new AstBuilder(); codeDomBuilder.AddField(field); - codeDomBuilder.GenerateCode(output); + codeDomBuilder.GenerateCode(output, transformAbortCondition); } public override void DecompileEvent(EventDefinition ev, ITextOutput output, DecompilationOptions options) { AstBuilder codeDomBuilder = new AstBuilder(); codeDomBuilder.AddEvent(ev); - codeDomBuilder.GenerateCode(output); + codeDomBuilder.GenerateCode(output, transformAbortCondition); } public override void DecompileType(TypeDefinition type, ITextOutput output, DecompilationOptions options) { AstBuilder codeDomBuilder = new AstBuilder(); codeDomBuilder.AddType(type); - codeDomBuilder.GenerateCode(output); + codeDomBuilder.GenerateCode(output, transformAbortCondition); } public override string TypeToString(TypeReference type, bool includeNamespace, ICustomAttributeProvider typeAttributes) diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index ea9ce1f0f..6a2e2140d 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -237,8 +237,5 @@ <Folder Include="TreeNodes" /> <Folder Include="TextView" /> </ItemGroup> - <ItemGroup> - <Content Include="ProfilingSessions\Session20110214_003955.sdps" /> - </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.Targets" /> </Project> \ No newline at end of file diff --git a/ILSpy/Language.cs b/ILSpy/Language.cs index fe1b34d85..ea4306dc7 100644 --- a/ILSpy/Language.cs +++ b/ILSpy/Language.cs @@ -120,11 +120,15 @@ namespace ICSharpCode.ILSpy /// <summary> /// A list of all languages. /// </summary> - public static readonly ReadOnlyCollection<Language> AllLanguages = Array.AsReadOnly( + public static readonly ReadOnlyCollection<Language> AllLanguages = new List<Language>( new Language[] { new CSharpLanguage(), new ILLanguage(true) - }); + } + #if DEBUG + .Concat(CSharpLanguage.GetDebugLanguages()) + #endif + ).AsReadOnly(); /// <summary> /// Gets a language using its name. diff --git a/ILSpy/MainWindow.xaml b/ILSpy/MainWindow.xaml index a79644072..c9423e9aa 100644 --- a/ILSpy/MainWindow.xaml +++ b/ILSpy/MainWindow.xaml @@ -11,6 +11,7 @@ MinHeight="200" UseLayoutRounding="True" TextOptions.TextFormattingMode="Display" + Icon="pack://application:,,,/ILSpy;component/images/Find.png" FocusManager.FocusedElement="{Binding ElementName=treeView}" > <Window.Resources> diff --git a/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/AstComparer.cs b/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/AstComparer.cs index ab74a4fc5..61d6fd4f1 100644 --- a/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/AstComparer.cs +++ b/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/AstComparer.cs @@ -31,6 +31,8 @@ namespace ICSharpCode.NRefactory.CSharp bool? result = true; while (result != false && (child1 != null || child2 != null)) { result &= AreEqual(child1, child2); + child1 = child1.NextSibling; + child2 = child2.NextSibling; } if (nodeTypesWithoutExtraInfo.Contains(nodeType)) return result;