From 614bce445a78b0c0e9c9eac3c348bbe2606744c0 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 6 Feb 2011 14:25:54 +0100 Subject: [PATCH] Add length limit to AvalonEdit output; added support for decompiling directly to disk. --- ILSpy/CSharpLanguage.cs | 4 + ILSpy/ILLanguage.cs | 4 +- ILSpy/ILSpy.csproj | 7 +- ILSpy/Images/Images.cs | 3 + ILSpy/Images/SaveFile.png | Bin 0 -> 501 bytes ILSpy/Images/ViewCode.png | Bin 0 -> 275 bytes ILSpy/Language.cs | 3 +- ILSpy/MainWindow.xaml | 6 + ILSpy/MainWindow.xaml.cs | 11 +- ILSpy/TextView/DecompilerTextView.cs | 254 +++++++++++++++--- .../TextView/OutputLengthExceededException.cs | 46 ++++ ILSpy/TextView/SmartTextOutput.cs | 25 +- ILSpy/TextView/UIElementGenerator.cs | 65 +++++ 13 files changed, 377 insertions(+), 51 deletions(-) create mode 100644 ILSpy/Images/SaveFile.png create mode 100644 ILSpy/Images/ViewCode.png create mode 100644 ILSpy/TextView/OutputLengthExceededException.cs create mode 100644 ILSpy/TextView/UIElementGenerator.cs diff --git a/ILSpy/CSharpLanguage.cs b/ILSpy/CSharpLanguage.cs index af2a6d707..0c4c39f5f 100644 --- a/ILSpy/CSharpLanguage.cs +++ b/ILSpy/CSharpLanguage.cs @@ -31,6 +31,10 @@ namespace ICSharpCode.ILSpy get { return "C#"; } } + public override string FileExtension { + get { return ".cs"; } + } + public override void DecompileMethod(MethodDefinition method, ITextOutput output, DecompilationOptions options) { throw new NotImplementedException(); diff --git a/ILSpy/ILLanguage.cs b/ILSpy/ILLanguage.cs index 2b1984524..069e6b39a 100644 --- a/ILSpy/ILLanguage.cs +++ b/ILSpy/ILLanguage.cs @@ -39,8 +39,8 @@ namespace ICSharpCode.ILSpy get { return detectControlStructure ? "IL (structured)" : "IL"; } } - public override ICSharpCode.AvalonEdit.Highlighting.IHighlightingDefinition SyntaxHighlighting { - get { return ICSharpCode.AvalonEdit.Highlighting.HighlightingManager.Instance.GetDefinition("ILAsm"); } + public override string FileExtension { + get { return ".il"; } } public override void DecompileMethod(MethodDefinition method, ITextOutput output, DecompilationOptions options) diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index 309936e65..5e947cc24 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -101,6 +101,8 @@ + + @@ -111,8 +113,10 @@ + + @@ -204,8 +208,5 @@ - - - \ No newline at end of file diff --git a/ILSpy/Images/Images.cs b/ILSpy/Images/Images.cs index 56313f3f7..30e987862 100644 --- a/ILSpy/Images/Images.cs +++ b/ILSpy/Images/Images.cs @@ -15,6 +15,9 @@ namespace ICSharpCode.ILSpy return image; } + public static readonly BitmapImage ViewCode = LoadBitmap("ViewCode"); + public static readonly BitmapImage Save = LoadBitmap("SaveFile"); + public static readonly BitmapImage Assembly = LoadBitmap("Assembly"); public static readonly BitmapImage AssemblyWarning = LoadBitmap("AssemblyWarning"); public static readonly BitmapImage Library = LoadBitmap("Library"); diff --git a/ILSpy/Images/SaveFile.png b/ILSpy/Images/SaveFile.png new file mode 100644 index 0000000000000000000000000000000000000000..81acdcb989554a537d6f8d2866dc9365ef767f53 GIT binary patch literal 501 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9EWV9eN~w38hu$dc~p z>&U>cv9IQL;A9|QA=x9ymw};5m4Tt5nStTwe<1ymfuYoZf#FpG1B2BJ1_tr`N%2SB z7#J9{JzX3_DsCnH`TyUZS(QPdA;Q5W^7p#`=6&`F8@7ihZ`hD9;U{3zV;R-wU1T3R}J1uKWb8n%e2D51AZ z4Gd0(53cFPwRukNdvui{sF{a>siSoQlM_&z0Z+xhKRV8(Ta6F@{QO>G$r1^Px<5P4 zA2@pSsDkq1GZQ8i&1jrCb0(w2|AMa!j7M6|Uitq&QZj7)KK35JjxKLcPO n^-Ou@*1(XIv_OD?MS`L6NuuG)S4V7sQN-Zs>gTe~DWM4fmUX>M literal 0 HcmV?d00001 diff --git a/ILSpy/Images/ViewCode.png b/ILSpy/Images/ViewCode.png new file mode 100644 index 0000000000000000000000000000000000000000..1b8949fb6ec432763ba6da3dd1e004ba830f4963 GIT binary patch literal 275 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCijSl0AZa85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=d3- zBeIx*fg5N8w*cc><-Mgq!5U8&#}Etuy^}9;HW=`@G^LiB1e# z$HucKh-+tz6SGV5-7*)AlMO#DK1@1&_>9fckO1ZIUT&Yym&zs_E%sI~ac!UaY69Ob zCNJqp4(B!2_7brtk^yhIuiLLUeWX2b(di!Mr59t<6n3P~ Q0=k32)78&qol`;+0KDN^n*aa+ literal 0 HcmV?d00001 diff --git a/ILSpy/Language.cs b/ILSpy/Language.cs index 92ac30b07..903f32159 100644 --- a/ILSpy/Language.cs +++ b/ILSpy/Language.cs @@ -30,9 +30,10 @@ namespace ICSharpCode.ILSpy public abstract class Language { public abstract string Name { get; } + public abstract string FileExtension { get; } public virtual ICSharpCode.AvalonEdit.Highlighting.IHighlightingDefinition SyntaxHighlighting { - get { return ICSharpCode.AvalonEdit.Highlighting.HighlightingManager.Instance.GetDefinition(this.Name); } + get { return ICSharpCode.AvalonEdit.Highlighting.HighlightingManager.Instance.GetDefinitionByExtension(this.FileExtension); } } public virtual void DecompileMethod(MethodDefinition method, ITextOutput output, DecompilationOptions options) diff --git a/ILSpy/MainWindow.xaml b/ILSpy/MainWindow.xaml index f08f929b4..05e70539f 100644 --- a/ILSpy/MainWindow.xaml +++ b/ILSpy/MainWindow.xaml @@ -25,6 +25,12 @@ + + + + + + diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index 0c586a877..2ac7bdeec 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -280,7 +280,16 @@ namespace ICSharpCode.ILSpy void TreeView_SelectionChanged(object sender, SelectionChangedEventArgs e) { - decompilerTextView.Decompile(sessionSettings.FilterSettings.Language, treeView.SelectedItems.OfType()); + decompilerTextView.Decompile(sessionSettings.FilterSettings.Language, + treeView.GetTopLevelSelection().OfType(), + new DecompilationOptions()); + } + + void saveCode_Click(object sender, RoutedEventArgs e) + { + decompilerTextView.SaveToDisk(sessionSettings.FilterSettings.Language, + treeView.GetTopLevelSelection().OfType(), + new DecompilationOptions()); } protected override void OnStateChanged(EventArgs e) diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs index 1d89ff7b5..e137495f7 100644 --- a/ILSpy/TextView/DecompilerTextView.cs +++ b/ILSpy/TextView/DecompilerTextView.cs @@ -18,20 +18,25 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Threading; using System.Xml; + using ICSharpCode.AvalonEdit.Folding; using ICSharpCode.AvalonEdit.Highlighting; using ICSharpCode.AvalonEdit.Highlighting.Xshd; +using ICSharpCode.Decompiler; using ICSharpCode.ILSpy.TreeNodes; +using Microsoft.Win32; using Mono.Cecil; namespace ICSharpCode.ILSpy.TextView @@ -42,12 +47,14 @@ namespace ICSharpCode.ILSpy.TextView sealed partial class DecompilerTextView : UserControl { readonly ReferenceElementGenerator referenceElementGenerator; + readonly UIElementGenerator uiElementGenerator; readonly FoldingManager foldingManager; internal MainWindow mainWindow; DefinitionLookup definitionLookup; CancellationTokenSource currentCancellationTokenSource; + #region Constructor public DecompilerTextView() { HighlightingManager.Instance.RegisterHighlighting( @@ -63,11 +70,15 @@ namespace ICSharpCode.ILSpy.TextView InitializeComponent(); this.referenceElementGenerator = new ReferenceElementGenerator(this); textEditor.TextArea.TextView.ElementGenerators.Add(referenceElementGenerator); + this.uiElementGenerator = new UIElementGenerator(); + textEditor.TextArea.TextView.ElementGenerators.Add(uiElementGenerator); textEditor.Text = "Welcome to ILSpy!"; foldingManager = FoldingManager.Install(textEditor.TextArea); } + #endregion - public void Decompile(ILSpy.Language language, IEnumerable treeNodes) + #region RunWithCancellation + void RunWithCancellation(Func> taskCreation, Action> taskCompleted) { if (waitAdorner.Visibility != Visibility.Visible) { waitAdorner.Visibility = Visibility.Visible; @@ -80,35 +91,13 @@ namespace ICSharpCode.ILSpy.TextView if (previousCancellationTokenSource != null) previousCancellationTokenSource.Cancel(); - DecompilationOptions options = new DecompilationOptions(); - options.CancellationToken = myCancellationTokenSource.Token; - - var task = RunDecompiler(language, treeNodes.ToArray(), options); + var task = taskCreation(myCancellationTokenSource.Token); Action continuation = delegate { try { if (currentCancellationTokenSource == myCancellationTokenSource) { currentCancellationTokenSource = null; waitAdorner.Visibility = Visibility.Collapsed; - textEditor.ScrollToHome(); - foldingManager.Clear(); - try { - SmartTextOutput textOutput = task.Result; - referenceElementGenerator.References = textOutput.References; - definitionLookup = textOutput.DefinitionLookup; - textEditor.SyntaxHighlighting = language.SyntaxHighlighting; - textEditor.Text = textOutput.ToString(); - foldingManager.UpdateFoldings(textOutput.Foldings.OrderBy(f => f.StartOffset), -1); - } catch (AggregateException aggregateException) { - textEditor.SyntaxHighlighting = null; - referenceElementGenerator.References = null; - definitionLookup = null; - // Unpack aggregate exceptions as long as there's only a single exception: - // (assembly load errors might produce nested aggregate exceptions) - Exception ex = aggregateException; - while (ex is AggregateException && (ex as AggregateException).InnerExceptions.Count == 1) - ex = ex.InnerException; - textEditor.Text = ex.ToString(); - } + taskCompleted(task); } else { try { task.Wait(); @@ -123,8 +112,71 @@ namespace ICSharpCode.ILSpy.TextView task.ContinueWith(delegate { Dispatcher.BeginInvoke(DispatcherPriority.Normal, continuation); }); } - static Task RunDecompiler(ILSpy.Language language, ILSpyTreeNodeBase[] nodes, DecompilationOptions options) + void cancelButton_Click(object sender, RoutedEventArgs e) { + if (currentCancellationTokenSource != null) + currentCancellationTokenSource.Cancel(); + } + #endregion + + #region ShowOutput + void ShowOutput(SmartTextOutput textOutput, ILSpy.Language language = null) + { + textEditor.ScrollToHome(); + foldingManager.Clear(); + uiElementGenerator.UIElements = textOutput.UIElements; + referenceElementGenerator.References = textOutput.References; + definitionLookup = textOutput.DefinitionLookup; + textEditor.SyntaxHighlighting = language != null ? language.SyntaxHighlighting : null; + textEditor.Text = textOutput.ToString(); + foldingManager.UpdateFoldings(textOutput.Foldings.OrderBy(f => f.StartOffset), -1); + } + #endregion + + #region Decompile (for display) + const int defaultOutputLengthLimit = 5000000; // more than 5M characters is too slow to output (when user browses treeview) + const int extendedOutputLengthLimit = 75000000; // more than 75M characters can get us into trouble with memory usage + + public void Decompile(ILSpy.Language language, IEnumerable treeNodes, DecompilationOptions options) + { + Decompile(language, treeNodes.ToArray(), defaultOutputLengthLimit, options); + } + + void Decompile(ILSpy.Language language, ILSpyTreeNodeBase[] treeNodes, int outputLengthLimit, DecompilationOptions options) + { + RunWithCancellation( + delegate (CancellationToken ct) { // creation of the background task + options.CancellationToken = ct; + return RunDecompiler(language, treeNodes, options, outputLengthLimit); + }, + delegate (Task task) { // handling the result + try { + SmartTextOutput textOutput = task.Result; + Debug.WriteLine("Decompiler finished; output size = {0} characters", textOutput.TextLength); + ShowOutput(textOutput, language); + } catch (AggregateException aggregateException) { + textEditor.SyntaxHighlighting = null; + Debug.WriteLine("Decompiler crashed: " + aggregateException.ToString()); + // Unpack aggregate exceptions as long as there's only a single exception: + // (assembly load errors might produce nested aggregate exceptions) + Exception ex = aggregateException; + while (ex is AggregateException && (ex as AggregateException).InnerExceptions.Count == 1) + ex = ex.InnerException; + if (ex is OutputLengthExceededException) { + ShowOutputLengthExceededMessage(language, treeNodes, options, outputLengthLimit == defaultOutputLengthLimit); + } else { + SmartTextOutput output = new SmartTextOutput(); + output.WriteLine(ex.ToString()); + ShowOutput(output); + } + } + }); + } + + static Task RunDecompiler(ILSpy.Language language, ILSpyTreeNodeBase[] nodes, DecompilationOptions options, int outputLengthLimit) + { + Debug.WriteLine("Start decompilation of {0} nodes", nodes.Length); + if (nodes.Length == 0) { // If there's nothing to be decompiled, don't bother starting up a thread. // (Improves perf in some cases since we don't have to wait for the thread-pool to accept our task) @@ -136,16 +188,77 @@ namespace ICSharpCode.ILSpy.TextView return Task.Factory.StartNew( delegate { SmartTextOutput textOutput = new SmartTextOutput(); - bool first = true; - foreach (var node in nodes) { - if (first) first = false; else textOutput.WriteLine(); - options.CancellationToken.ThrowIfCancellationRequested(); - node.Decompile(language, textOutput, options); - } + textOutput.LengthLimit = outputLengthLimit; + DecompileNodes(language, nodes, options, textOutput); return textOutput; }); } + static void DecompileNodes(ILSpy.Language language, ILSpyTreeNodeBase[] nodes, DecompilationOptions options, ITextOutput textOutput) + { + bool first = true; + foreach (var node in nodes) { + if (first) first = false; else textOutput.WriteLine(); + options.CancellationToken.ThrowIfCancellationRequested(); + node.Decompile(language, textOutput, options); + } + } + #endregion + + #region ShowOutputLengthExceededMessage + void ShowOutputLengthExceededMessage(ILSpy.Language language, ILSpyTreeNodeBase[] treeNodes, DecompilationOptions options, bool wasNormalLimit) + { + SmartTextOutput output = new SmartTextOutput(); + if (wasNormalLimit) { + output.WriteLine("You have selected too much code for it to be displayed automatically."); + } else { + output.WriteLine("You have selected too much code; it cannot be displayed here."); + } + output.WriteLine(); + Button button; + if (wasNormalLimit) { + output.AddUIElement(MakeButton( + Images.ViewCode, "Display Code", + delegate { + Decompile(language, treeNodes, extendedOutputLengthLimit, options); + })); + output.WriteLine(); + } + + output.AddUIElement(MakeButton( + Images.Save, "Save Code", + delegate { + SaveToDisk(language, treeNodes, options); + })); + output.WriteLine(); + + ShowOutput(output); + } + + Func