From 7d1d16831342b740dfc8fb3a4a2404786d0cb00e Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 6 Mar 2011 00:21:01 +0100 Subject: [PATCH 01/15] Add options dialog. --- .../Ast/DecompilerContext.cs | 1 + .../Ast/Transforms/DelegateConstruction.cs | 3 + ICSharpCode.Decompiler/DecompilerSettings.cs | 55 ++++++++++ .../ICSharpCode.Decompiler.csproj | 2 + .../ILAst/YieldReturnDecompiler.cs | 15 +++ .../Tests/ICSharpCode.Decompiler.Tests.csproj | 1 + ICSharpCode.Decompiler/Tests/YieldReturn.cs | 103 ++++++++++++++++++ ILSpy/CSharpLanguage.cs | 3 +- ILSpy/DecompilationOptions.cs | 11 ++ ILSpy/DecompilerSettingsPanel.xaml | 9 ++ ILSpy/DecompilerSettingsPanel.xaml.cs | 57 ++++++++++ ILSpy/ILSpy.csproj | 10 ++ ILSpy/MainWindow.xaml.cs | 5 + ILSpy/OptionsDialog.xaml | 21 ++++ ILSpy/OptionsDialog.xaml.cs | 93 ++++++++++++++++ 15 files changed, 388 insertions(+), 1 deletion(-) create mode 100644 ICSharpCode.Decompiler/DecompilerSettings.cs create mode 100644 ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs create mode 100644 ICSharpCode.Decompiler/Tests/YieldReturn.cs create mode 100644 ILSpy/DecompilerSettingsPanel.xaml create mode 100644 ILSpy/DecompilerSettingsPanel.xaml.cs create mode 100644 ILSpy/OptionsDialog.xaml create mode 100644 ILSpy/OptionsDialog.xaml.cs diff --git a/ICSharpCode.Decompiler/Ast/DecompilerContext.cs b/ICSharpCode.Decompiler/Ast/DecompilerContext.cs index 7ea5c0b02..9235e7698 100644 --- a/ICSharpCode.Decompiler/Ast/DecompilerContext.cs +++ b/ICSharpCode.Decompiler/Ast/DecompilerContext.cs @@ -12,6 +12,7 @@ namespace ICSharpCode.Decompiler public CancellationToken CancellationToken; public TypeDefinition CurrentType; public MethodDefinition CurrentMethod; + public DecompilerSettings Settings; public DecompilerContext Clone() { diff --git a/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs b/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs index c0fcabab3..7684b1702 100644 --- a/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs @@ -105,6 +105,9 @@ namespace ICSharpCode.Decompiler.Ast.Transforms bool HandleAnonymousMethod(ObjectCreateExpression objectCreateExpression, Expression target, MethodReference methodRef) { + if (!context.Settings.AnonymousMethods) + return false; // anonymous method decompilation is disabled + // Anonymous methods are defined in the same assembly, so there's no need to Resolve(). MethodDefinition method = methodRef as MethodDefinition; if (!IsAnonymousMethod(context, method)) diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs new file mode 100644 index 000000000..53736311a --- /dev/null +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -0,0 +1,55 @@ +// 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.ComponentModel; + +namespace ICSharpCode.Decompiler +{ + /// + /// Settings for the decompiler. + /// + public class DecompilerSettings : INotifyPropertyChanged + { + bool anonymousMethods = true; + + /// + /// Decompile anonymous methods/lambdas. + /// + public bool AnonymousMethods { + get { return anonymousMethods; } + set { + if (anonymousMethods != value) { + anonymousMethods = value; + OnPropertyChanged("AnonymousMethods"); + } + } + } + + bool yieldReturn = true; + + /// + /// Decompile enumerators. + /// + public bool YieldReturn { + get { return yieldReturn; } + set { + if (yieldReturn != value) { + yieldReturn = value; + OnPropertyChanged("YieldReturn"); + } + } + } + + public event EventHandler YieldReturnChanged; + + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged(string propertyName) + { + if (PropertyChanged != null) { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + } + } +} diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 3896d43f1..104543360 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -66,6 +66,7 @@ + @@ -95,6 +96,7 @@ + diff --git a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs new file mode 100644 index 000000000..b1ec1b78b --- /dev/null +++ b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs @@ -0,0 +1,15 @@ +// 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; + +namespace ICSharpCode.Decompiler.ILAst +{ + public class YieldReturnDecompiler + { + // For a description on the code generated by the C# compiler for yield return: + // http://csharpindepth.com/Articles/Chapter6/IteratorBlockImplementation.aspx + + // not implemented yet... + } +} diff --git a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj index a24ef753b..42ee0a0cd 100644 --- a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj @@ -53,6 +53,7 @@ + diff --git a/ICSharpCode.Decompiler/Tests/YieldReturn.cs b/ICSharpCode.Decompiler/Tests/YieldReturn.cs new file mode 100644 index 000000000..de9bd1efb --- /dev/null +++ b/ICSharpCode.Decompiler/Tests/YieldReturn.cs @@ -0,0 +1,103 @@ +// 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.Collections.Generic; + +public static class YieldReturn +{ + public static IEnumerable SimpleYieldReturn() + { + yield return "A"; + yield return "B"; + yield return "C"; + } + + public static IEnumerable YieldReturnInLoop() + { + for (int i = 0; i < 100; i++) { + yield return i; + } + } + + public static IEnumerable YieldReturnWithTryFinally() + { + yield return 0; + try { + yield return 1; + } finally { + Console.WriteLine("Finally!"); + } + yield return 2; + } + + + public static IEnumerable YieldReturnWithNestedTryFinally(bool breakInMiddle) + { + Console.WriteLine("Start of method - 1"); + yield return "Start of method"; + Console.WriteLine("Start of method - 2"); + try { + Console.WriteLine("Within outer try - 1"); + yield return "Within outer try"; + Console.WriteLine("Within outer try - 2"); + try { + Console.WriteLine("Within inner try - 1"); + yield return "Within inner try"; + Console.WriteLine("Within inner try - 2"); + if (breakInMiddle) + yield break; + Console.WriteLine("End of inner try - 1"); + yield return "End of inner try"; + Console.WriteLine("End of inner try - 2"); + } finally { + Console.WriteLine("Inner Finally"); + } + Console.WriteLine("End of outer try - 1"); + yield return "End of outer try"; + Console.WriteLine("End of outer try - 2"); + } finally { + Console.WriteLine("Outer Finally"); + } + Console.WriteLine("End of method - 1"); + yield return "End of method"; + Console.WriteLine("End of method - 2"); + } + + public static IEnumerable YieldReturnWithTwoNonNestedFinallyBlocks(IEnumerable input) + { + // outer try-finally block + foreach (string line in input) { + // nested try-finally block + try { + yield return line; + } finally { + Console.WriteLine("Processed " + line); + } + } + yield return "A"; + yield return "B"; + yield return "C"; + yield return "D"; + yield return "E"; + yield return "F"; + // outer try-finally block + foreach (string line in input) + yield return line.ToUpper(); + } + + public static IEnumerable> YieldReturnWithAnonymousMethods1(IEnumerable input) + { + foreach (string line in input) { + yield return () => line; + } + } + + public static IEnumerable> YieldReturnWithAnonymousMethods2(IEnumerable input) + { + foreach (string line in input) { + string copy = line; + yield return () => copy; + } + } +} diff --git a/ILSpy/CSharpLanguage.cs b/ILSpy/CSharpLanguage.cs index c01caaf1c..60fc12388 100644 --- a/ILSpy/CSharpLanguage.cs +++ b/ILSpy/CSharpLanguage.cs @@ -376,7 +376,8 @@ namespace ICSharpCode.ILSpy return new AstBuilder( new DecompilerContext { CancellationToken = options.CancellationToken, - CurrentType = currentType + CurrentType = currentType, + Settings = options.DecompilerSettings }); } diff --git a/ILSpy/DecompilationOptions.cs b/ILSpy/DecompilationOptions.cs index 8dc953c6d..bcd59af6b 100644 --- a/ILSpy/DecompilationOptions.cs +++ b/ILSpy/DecompilationOptions.cs @@ -18,6 +18,7 @@ using System; using System.Threading; +using ICSharpCode.Decompiler; namespace ICSharpCode.ILSpy { @@ -45,5 +46,15 @@ namespace ICSharpCode.ILSpy /// to allow for cooperative cancellation of the decompilation task. /// public CancellationToken CancellationToken { get; set; } + + /// + /// Gets the settings for the decompiler. + /// + public DecompilerSettings DecompilerSettings { get; set; } + + public DecompilationOptions() + { + this.DecompilerSettings = DecompilerSettingsPanel.LoadDecompilerSettings(ILSpySettings.Load()); + } } } diff --git a/ILSpy/DecompilerSettingsPanel.xaml b/ILSpy/DecompilerSettingsPanel.xaml new file mode 100644 index 000000000..00732070c --- /dev/null +++ b/ILSpy/DecompilerSettingsPanel.xaml @@ -0,0 +1,9 @@ + + + Decompile anonymous methods/lambdas + Decompile enumerators (yield return) + + \ No newline at end of file diff --git a/ILSpy/DecompilerSettingsPanel.xaml.cs b/ILSpy/DecompilerSettingsPanel.xaml.cs new file mode 100644 index 000000000..694ad1040 --- /dev/null +++ b/ILSpy/DecompilerSettingsPanel.xaml.cs @@ -0,0 +1,57 @@ +// 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.Collections.Generic; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Xml.Linq; +using ICSharpCode.Decompiler; + +namespace ICSharpCode.ILSpy +{ + /// + /// Interaction logic for DecompilerSettingsPanel.xaml + /// + [ExportOptionPage("Decompiler")] + partial class DecompilerSettingsPanel : UserControl, IOptionPage + { + public DecompilerSettingsPanel() + { + InitializeComponent(); + } + + public void Load(ILSpySettings settings) + { + this.DataContext = LoadDecompilerSettings(settings); + } + + public static DecompilerSettings LoadDecompilerSettings(ILSpySettings settings) + { + XElement e = settings["DecompilerSettings"]; + DecompilerSettings s = new DecompilerSettings(); + s.AnonymousMethods = (bool?)e.Attribute("anonymousMethods") ?? s.AnonymousMethods; + s.YieldReturn = (bool?)e.Attribute("yieldReturn") ?? s.YieldReturn; + return s; + } + + public void Save(XElement root) + { + DecompilerSettings s = (DecompilerSettings)this.DataContext; + XElement section = new XElement("DecompilerSettings"); + section.SetAttributeValue("anonymousMethods", s.AnonymousMethods); + section.SetAttributeValue("yieldReturn", s.YieldReturn); + + XElement existingElement = root.Element("DecompilerSettings"); + if (existingElement != null) + existingElement.ReplaceWith(section); + else + root.Add(section); + } + } +} \ No newline at end of file diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index 41598a0a1..b094e107b 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -89,6 +89,10 @@ + + DecompilerSettingsPanel.xaml + Code + @@ -111,6 +115,10 @@ OpenFromGacDialog.xaml Code + + OptionsDialog.xaml + Code + README.txt @@ -168,8 +176,10 @@ SearchBox.cs + + DecompilerTextView.cs diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index 5056a19a9..90867ade9 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -429,6 +429,11 @@ namespace ICSharpCode.ILSpy decompilerTextView.Decompile(this.CurrentLanguage, this.SelectedNodes, new DecompilationOptions()); } + public void RefreshDecompiledView() + { + TreeView_SelectionChanged(null, null); + } + public DecompilerTextView TextView { get { return decompilerTextView; } } diff --git a/ILSpy/OptionsDialog.xaml b/ILSpy/OptionsDialog.xaml new file mode 100644 index 000000000..c5a9b8fe7 --- /dev/null +++ b/ILSpy/OptionsDialog.xaml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/ILSpy/OptionsDialog.xaml.cs b/ILSpy/OptionsDialog.xaml.cs new file mode 100644 index 000000000..dc87b88bf --- /dev/null +++ b/ILSpy/OptionsDialog.xaml.cs @@ -0,0 +1,93 @@ +// 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.Collections.Generic; +using System.ComponentModel.Composition; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Xml.Linq; + +namespace ICSharpCode.ILSpy +{ + /// + /// Interaction logic for OptionsDialog.xaml + /// + public partial class OptionsDialog : Window + { + [ImportMany("OptionPages", typeof(UIElement), RequiredCreationPolicy = CreationPolicy.NonShared)] + Lazy[] optionPages = null; + + public OptionsDialog() + { + InitializeComponent(); + App.CompositionContainer.ComposeParts(this); + ILSpySettings settings = ILSpySettings.Load(); + foreach (var optionPage in optionPages) { + TabItem tabItem = new TabItem(); + tabItem.Header = optionPage.Metadata.Title; + tabItem.Content = optionPage.Value; + tabControl.Items.Add(tabItem); + + IOptionPage page = optionPage.Value as IOptionPage; + if (page != null) + page.Load(settings); + } + } + + void OKButton_Click(object sender, RoutedEventArgs e) + { + ILSpySettings.Update( + delegate (XElement root) { + foreach (var optionPage in optionPages) { + IOptionPage page = optionPage.Value as IOptionPage; + if (page != null) + page.Save(root); + } + }); + this.DialogResult = true; + Close(); + } + } + + public interface IOptionsMetadata + { + string Title { get; } + } + + public interface IOptionPage + { + void Load(ILSpySettings settings); + void Save(XElement root); + } + + [MetadataAttribute] + [AttributeUsage(AttributeTargets.Class, AllowMultiple=false)] + public class ExportOptionPageAttribute : ExportAttribute + { + public ExportOptionPageAttribute(string title) + : base("OptionPages", typeof(UIElement)) + { + this.Title = title; + } + + public string Title { get; private set; } + } + + [ExportMainMenuCommand(Menu = "_View", Header = "_Options", MenuCategory = "Options", MenuOrder = 999)] + sealed class ShowOptionsCommand : SimpleCommand + { + public override void Execute(object parameter) + { + OptionsDialog dlg = new OptionsDialog(); + dlg.Owner = MainWindow.Instance; + if (dlg.ShowDialog() == true) + MainWindow.Instance.RefreshDecompiledView(); + } + } +} \ No newline at end of file From 6feadf38404ee224f6579dfc0ad2ab4f2daf4a1d Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 6 Mar 2011 04:19:42 +0100 Subject: [PATCH 02/15] Started analysis of yield return statements. --- .../Ast/DecompilerContext.cs | 2 +- .../ICSharpCode.Decompiler.csproj | 1 + .../ILAst/DefaultDictionary.cs | 117 ++++++ ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs | 5 +- .../ILAst/ILAstOptimizer.cs | 4 + ICSharpCode.Decompiler/ILAst/ILAstTypes.cs | 10 + .../ILAst/YieldReturnDecompiler.cs | 387 +++++++++++++++++- 7 files changed, 522 insertions(+), 4 deletions(-) create mode 100644 ICSharpCode.Decompiler/ILAst/DefaultDictionary.cs diff --git a/ICSharpCode.Decompiler/Ast/DecompilerContext.cs b/ICSharpCode.Decompiler/Ast/DecompilerContext.cs index 9235e7698..69e098531 100644 --- a/ICSharpCode.Decompiler/Ast/DecompilerContext.cs +++ b/ICSharpCode.Decompiler/Ast/DecompilerContext.cs @@ -12,7 +12,7 @@ namespace ICSharpCode.Decompiler public CancellationToken CancellationToken; public TypeDefinition CurrentType; public MethodDefinition CurrentMethod; - public DecompilerSettings Settings; + public DecompilerSettings Settings = new DecompilerSettings(); public DecompilerContext Clone() { diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 104543360..f4e809633 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -87,6 +87,7 @@ + diff --git a/ICSharpCode.Decompiler/ILAst/DefaultDictionary.cs b/ICSharpCode.Decompiler/ILAst/DefaultDictionary.cs new file mode 100644 index 000000000..e35bb868d --- /dev/null +++ b/ICSharpCode.Decompiler/ILAst/DefaultDictionary.cs @@ -0,0 +1,117 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace ICSharpCode.Decompiler.ILAst +{ + /// + /// Dictionary with default values. + /// + sealed class DefaultDictionary : IDictionary + { + readonly IDictionary dict; + readonly Func defaultProvider; + + public DefaultDictionary(TValue defaultValue, IDictionary dictionary = null) + : this(key => defaultValue, dictionary) + { + } + + public DefaultDictionary(Func defaultProvider = null, IDictionary dictionary = null) + { + this.dict = dictionary ?? new Dictionary(); + this.defaultProvider = defaultProvider ?? (key => default(TValue)); + } + + public TValue this[TKey key] { + get { + TValue val; + if (dict.TryGetValue(key, out val)) + return val; + else + return dict[key] = defaultProvider(key); + } + set { + dict[key] = value; + } + } + + public ICollection Keys { + get { return dict.Keys; } + } + + public ICollection Values { + get { return dict.Values; } + } + + public int Count { + get { return dict.Count; } + } + + bool ICollection>.IsReadOnly { + get { return false; } + } + + public bool ContainsKey(TKey key) + { + return dict.ContainsKey(key); + } + + public void Add(TKey key, TValue value) + { + dict.Add(key, value); + } + + public bool Remove(TKey key) + { + return dict.Remove(key); + } + + public bool TryGetValue(TKey key, out TValue value) + { + return dict.TryGetValue(key, out value); + } + + void ICollection>.Add(KeyValuePair item) + { + dict.Add(item); + } + + public void Clear() + { + dict.Clear(); + } + + bool ICollection>.Contains(KeyValuePair item) + { + return dict.Contains(item); + } + + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + { + dict.CopyTo(array, arrayIndex); + } + + bool ICollection>.Remove(KeyValuePair item) + { + return dict.Remove(item); + } + + IEnumerator> IEnumerable>.GetEnumerator() + { + return dict.GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return dict.GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs b/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs index 28b5bc317..b08f11fa1 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs @@ -609,9 +609,10 @@ namespace ICSharpCode.Decompiler.ILAst tryCatchBlock.CatchBlocks.Add(catchBlock); } else if (eh.HandlerType == ExceptionHandlerType.Finally) { tryCatchBlock.FinallyBlock = new ILBlock(handlerAst); - // TODO: ldexception + } else if (eh.HandlerType == ExceptionHandlerType.Fault) { + tryCatchBlock.FaultBlock = new ILBlock(handlerAst); } else { - // TODO + // TODO: ExceptionHandlerType.Filter } } diff --git a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs index 36c0756c4..fa60a5e1d 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs @@ -12,6 +12,7 @@ namespace ICSharpCode.Decompiler.ILAst public enum ILAstOptimizationStep { ReduceBranchInstructionSet, + YieldReturn, SplitToMovableBlocks, PeepholeOptimizations, FindLoops, @@ -38,6 +39,9 @@ namespace ICSharpCode.Decompiler.ILAst ReduceBranchInstructionSet(block); } + if (abortBeforeStep == ILAstOptimizationStep.YieldReturn) return; + YieldReturnDecompiler.Run(context, method); + if (abortBeforeStep == ILAstOptimizationStep.SplitToMovableBlocks) return; foreach(ILBlock block in method.GetSelfAndChildrenRecursive().ToList()) { SplitToBasicBlocks(block); diff --git a/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs b/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs index cc7a65e04..dae74b272 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs @@ -150,6 +150,7 @@ namespace ICSharpCode.Decompiler.ILAst public ILBlock TryBlock; public List CatchBlocks; public ILBlock FinallyBlock; + public ILBlock FaultBlock; public override IEnumerable GetChildren() { @@ -158,6 +159,8 @@ namespace ICSharpCode.Decompiler.ILAst foreach (var catchBlock in this.CatchBlocks) { yield return catchBlock; } + if (this.FaultBlock != null) + yield return this.FaultBlock; if (this.FinallyBlock != null) yield return this.FinallyBlock; } @@ -172,6 +175,13 @@ namespace ICSharpCode.Decompiler.ILAst foreach (CatchBlock block in CatchBlocks) { block.WriteTo(output); } + if (FaultBlock != null) { + output.WriteLine("fault {"); + output.Indent(); + FaultBlock.WriteTo(output); + output.Unindent(); + output.WriteLine("}"); + } if (FinallyBlock != null) { output.WriteLine("finally {"); output.Indent(); diff --git a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs index b1ec1b78b..c2d61c0ca 100644 --- a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs +++ b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs @@ -2,6 +2,10 @@ // This code is distributed under MIT X11 license (for details please see \doc\license.txt) using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Mono.Cecil; namespace ICSharpCode.Decompiler.ILAst { @@ -10,6 +14,387 @@ namespace ICSharpCode.Decompiler.ILAst // For a description on the code generated by the C# compiler for yield return: // http://csharpindepth.com/Articles/Chapter6/IteratorBlockImplementation.aspx - // not implemented yet... + // The idea here is: + // - Figure out whether the current method is instanciating an enumerator + // - Figure out which of the fields is the state field + // - Construct an exception table based on states. This allows us to determine, for each state, what the parent try block is. + + /// + /// This exception is thrown when we find something else than we expect from the C# compiler. + /// This aborts the analysis and makes the whole transform fail. + /// + class YieldAnalysisFailedException : Exception {} + + DecompilerContext context; + TypeDefinition enumeratorType; + MethodDefinition enumeratorCtor; + FieldDefinition stateField; + Dictionary parameterToFieldMap; + + #region Run() method + public static void Run(DecompilerContext context, ILBlock method) + { + if (!context.Settings.YieldReturn) + return; // abort if enumerator decompilation is disabled + var yrd = new YieldReturnDecompiler(); + yrd.context = context; + if (!yrd.MatchEnumeratorCreationPattern(method)) + return; + yrd.enumeratorType = yrd.enumeratorCtor.DeclaringType; + #if !DEBUG + try { + #endif + yrd.AnalyzeCtor(); + yrd.ConstructExceptionTable(); + #if !DEBUG + } catch (YieldAnalysisFailedException) { + return; + } + #endif + } + #endregion + + #region Match the enumerator creation pattern + bool MatchEnumeratorCreationPattern(ILBlock method) + { + if (method.Body.Count == 0) + return false; + ILExpression ret; + if (method.Body.Count == 1) { + // ret(newobj(...)) + if (method.Body[0].Match(ILCode.Ret, out ret) && ret.Arguments.Count == 1) + return MatchEnumeratorCreationNewObj(ret.Arguments[0], out enumeratorCtor); + else + return false; + } + // stloc(var_1, newobj(..) + ILExpression stloc; + if (!method.Body[0].Match(ILCode.Stloc, out stloc)) + return false; + if (!MatchEnumeratorCreationNewObj(stloc.Arguments[0], out enumeratorCtor)) + return false; + + parameterToFieldMap = new Dictionary(); + int i = 1; + ILExpression stfld; + while (i < method.Body.Count && method.Body[i].Match(ILCode.Stfld, out stfld)) { + // stfld(..., ldloc(var_1), ldarg(...)) + ILExpression ldloc, ldarg; + if (!(stfld.Arguments[0].Match(ILCode.Ldloc, out ldloc) && stfld.Arguments[1].Match(ILCode.Ldarg, out ldarg))) + return false; + if (ldloc.Operand != stloc.Operand || !(stfld.Operand is FieldDefinition)) + return false; + parameterToFieldMap[(ParameterDefinition)ldarg.Operand] = (FieldDefinition)stfld.Operand; + i++; + } + ILExpression stloc2; + if (i < method.Body.Count && method.Body[i].Match(ILCode.Stloc, out stloc2)) { + // stloc(var_2, ldloc(var_1)) + if (stloc2.Arguments[0].Code != ILCode.Ldloc || stloc2.Arguments[0].Operand != stloc.Operand) + return false; + i++; + } else { + // the compiler might skip the above instruction in release builds; in that case, it directly returns stloc.Operand + stloc2 = stloc; + } + ILExpression br; + if (i + 1 < method.Body.Count && method.Body[i].Match(ILCode.Br, out br)) { + if (br.Operand != method.Body[i + 1]) + return false; + i += 2; + } + if (i < method.Body.Count && method.Body[i].Match(ILCode.Ret, out ret)) { + if (ret.Arguments[0].Code == ILCode.Ldloc && ret.Arguments[0].Operand == stloc2.Operand) { + return true; + } + } + return false; + } + + bool MatchEnumeratorCreationNewObj(ILExpression expr, out MethodDefinition ctor) + { + // newobj(CurrentType/...::.ctor, ldc.i4(-2)) + ctor = expr.Operand as MethodDefinition; + if (expr.Code != ILCode.Newobj || expr.Arguments.Count != 1) + return false; + if (expr.Arguments[0].Code != ILCode.Ldc_I4 || (int)expr.Arguments[0].Operand != -2) + return false; + if (ctor == null || !ctor.DeclaringType.IsCompilerGenerated()) + return false; + return ctor.DeclaringType.DeclaringType == context.CurrentType; + } + #endregion + + #region Figure out what the state field is + void AnalyzeCtor() + { + ILBlock method = CreateILAst(enumeratorCtor); + foreach (ILNode node in method.Body) { + ILExpression stfld; + if (node.Match(ILCode.Stfld, out stfld) + && stfld.Arguments[0].Code == ILCode.Ldarg && ((ParameterDefinition)stfld.Arguments[0].Operand).Index < 0 + && stfld.Arguments[1].Code == ILCode.Ldarg && ((ParameterDefinition)stfld.Arguments[1].Operand).Index == 0) + { + stateField = stfld.Operand as FieldDefinition; + } + } + if (stateField == null) + throw new YieldAnalysisFailedException(); + } + + ILBlock CreateILAst(MethodDefinition method) + { + if (method == null || !method.HasBody) + throw new YieldAnalysisFailedException(); + + ILBlock ilMethod = new ILBlock(); + ILAstBuilder astBuilder = new ILAstBuilder(); + ilMethod.Body = astBuilder.Build(method, true); + ILAstOptimizer optimizer = new ILAstOptimizer(); + optimizer.Optimize(context, ilMethod, ILAstOptimizationStep.YieldReturn); + return ilMethod; + } + #endregion + + #region Construction of the exception table + // We construct the exception table by analyzing the enumerator's Dispose() method. + + // Assumption: there are no loops/backward jumps + // We 'run' the code, with "state" being a symbolic variable + // so it can form expressions like "state + x" (when there's a sub instruction) + // For each instruction, we maintain a list of value ranges for state for which the instruction is reachable. + // This is (int.MinValue, int.MaxValue) for the first instruction. + // These ranges are propagated depending on the conditional jumps performed by the code. + + #region struct Interval / class StateRange + struct Interval + { + public readonly int Start, End; + + public Interval(int start, int end) + { + Debug.Assert(start <= end || (start == 0 && end == -1)); + this.Start = start; + this.End = end; + } + + public override string ToString() + { + return string.Format("({0} to {1})", Start, End); + } + } + + class StateRange + { + readonly List data = new List(); + + public StateRange() + { + } + + public StateRange(int start, int end) + { + this.data.Add(new Interval(start, end)); + } + + public void UnionWith(StateRange other) + { + data.AddRange(other.data); + } + + /// + /// Unions this state range with (other intersect (minVal to maxVal)) + /// + public void UnionWith(StateRange other, int minVal, int maxVal) + { + foreach (Interval v in other.data) { + int start = Math.Max(v.Start, minVal); + int end = Math.Min(v.End, maxVal); + if (start <= end) + data.Add(new Interval(start, end)); + } + } + + /// + /// Merges overlapping interval ranges. + /// + public void Simplify() + { + if (data.Count < 2) + return; + data.Sort((a, b) => a.Start.CompareTo(b.Start)); + Interval prev = data[0]; + int prevIndex = 0; + for (int i = 1; i < data.Count; i++) { + Interval next = data[i]; + Debug.Assert(prev.Start <= next.Start); + if (next.Start <= prev.End + 1) { // intervals overlapping or touching + prev = new Interval(prev.Start, Math.Max(prev.End, next.End)); + data[prevIndex] = prev; + data[i] = new Interval(0, -1); // mark as deleted + } else { + prev = next; + prevIndex = i; + } + } + data.RemoveAll(i => i.Start > i.End); // remove all entries that were marked as deleted + } + + public override string ToString() + { + return string.Join(",", data); + } + + public Interval ToInterval() + { + if (data.Count == 1) + return data[0]; + else + throw new YieldAnalysisFailedException(); + } + } + #endregion + + DefaultDictionary ranges; + + void ConstructExceptionTable() + { + MethodDefinition disposeMethod = enumeratorType.Methods.FirstOrDefault(m => m.Name == "System.IDisposable.Dispose"); + ILBlock ilMethod = CreateILAst(disposeMethod); + + ranges = new DefaultDictionary(node => new StateRange()); + ranges[ilMethod] = new StateRange(int.MinValue, int.MaxValue); + AssignStateRanges(ilMethod); + + // Now look at the finally blocks: + foreach (var tryFinally in ilMethod.GetSelfAndChildrenRecursive()) { + Interval interval = ranges[tryFinally.TryBlock.Body[0]].ToInterval(); + var finallyBody = tryFinally.FinallyBlock.Body; + if (!(finallyBody.Count == 2 || finallyBody.Count == 3)) + throw new YieldAnalysisFailedException(); + ILExpression call = finallyBody[0] as ILExpression; + if (call == null || call.Code != ILCode.Call || call.Arguments.Count != 1) + throw new YieldAnalysisFailedException(); + if (call.Arguments[0].Code != ILCode.Ldarg || ((ParameterDefinition)call.Arguments[0].Operand).Index >= 0) + throw new YieldAnalysisFailedException(); + if (finallyBody.Count == 3 && !finallyBody[1].Match(ILCode.Nop)) + throw new YieldAnalysisFailedException(); + if (!finallyBody[finallyBody.Count - 1].Match(ILCode.Endfinally)) + throw new YieldAnalysisFailedException(); + + Debug.WriteLine("State " + interval + " -> " + call.Operand.ToString()); + } + } + + #region Assign StateRanges / Symbolic Execution + void AssignStateRanges(ILBlock block) + { + if (block.Body.Count == 0) + return; + ranges[block.Body[0]].UnionWith(ranges[block]); + for (int i = 0; i < block.Body.Count; i++) { + StateRange nodeRange = ranges[block.Body[i]]; + nodeRange.Simplify(); + + ILLabel label = block.Body[i] as ILLabel; + if (label != null) { + ranges[block.Body[i + 1]].UnionWith(nodeRange); + continue; + } + + ILTryCatchBlock tryFinally = block.Body[i] as ILTryCatchBlock; + if (tryFinally != null) { + if (tryFinally.CatchBlocks.Count != 0 || tryFinally.FaultBlock != null || tryFinally.FinallyBlock == null) + throw new YieldAnalysisFailedException(); + ranges[tryFinally.TryBlock].UnionWith(nodeRange); + AssignStateRanges(tryFinally.TryBlock); + continue; + } + + ILExpression expr = block.Body[i] as ILExpression; + if (expr == null) + throw new YieldAnalysisFailedException(); + switch (expr.Code) { + case ILCode.Switch: + SymbolicValue val = Eval(expr.Arguments[0]); + if (val.Type != SymbolicValueType.State) + throw new YieldAnalysisFailedException(); + ILLabel[] targetLabels = (ILLabel[])expr.Operand; + for (int j = 0; j < targetLabels.Length; j++) { + int state = j - val.Constant; + ranges[targetLabels[j]].UnionWith(nodeRange, state, state); + } + ranges[block.Body[i + 1]].UnionWith(nodeRange, int.MinValue, -1 - val.Constant); + ranges[block.Body[i + 1]].UnionWith(nodeRange, targetLabels.Length - val.Constant, int.MaxValue); + break; + case ILCode.Br: + case ILCode.Leave: + ranges[(ILLabel)expr.Operand].UnionWith(nodeRange); + break; + case ILCode.Nop: + ranges[block.Body[i + 1]].UnionWith(nodeRange); + break; + case ILCode.Ret: + break; + default: + throw new YieldAnalysisFailedException(); + } + } + } + + enum SymbolicValueType + { + IntegerConstant, + State, + This + } + + struct SymbolicValue + { + public readonly int Constant; + public readonly SymbolicValueType Type; + + public SymbolicValue(SymbolicValueType type, int constant = 0) + { + this.Type = type; + this.Constant = constant; + } + + public override string ToString() + { + return string.Format("[SymbolicValue {0}: {1}]", this.Type, this.Constant); + } + } + + SymbolicValue Eval(ILExpression expr) + { + switch (expr.Code) { + case ILCode.Sub: + SymbolicValue left = Eval(expr.Arguments[0]); + SymbolicValue right = Eval(expr.Arguments[1]); + if (left.Type != SymbolicValueType.State && right.Type != SymbolicValueType.IntegerConstant) + throw new YieldAnalysisFailedException(); + if (right.Type != SymbolicValueType.IntegerConstant) + throw new YieldAnalysisFailedException(); + return new SymbolicValue(left.Type, unchecked ( left.Constant - right.Constant )); + case ILCode.Ldfld: + if (Eval(expr.Arguments[0]).Type != SymbolicValueType.This) + throw new YieldAnalysisFailedException(); + if (expr.Operand != stateField) + throw new YieldAnalysisFailedException(); + return new SymbolicValue(SymbolicValueType.State); + case ILCode.Ldarg: + if (((ParameterDefinition)expr.Operand).Index < 0) + return new SymbolicValue(SymbolicValueType.This); + else + throw new YieldAnalysisFailedException(); + case ILCode.Ldc_I4: + return new SymbolicValue(SymbolicValueType.IntegerConstant, (int)expr.Operand); + default: + throw new YieldAnalysisFailedException(); + } + } + #endregion + #endregion } } From d5505b784327c89bb08d44704fc051faeacee7dd Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 6 Mar 2011 06:40:58 +0100 Subject: [PATCH 03/15] Initial attempt at yield return transform. --- .../Ast/AstMethodBodyBuilder.cs | 7 +- ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs | 13 +- ICSharpCode.Decompiler/ILAst/ILAstTypes.cs | 7 + ICSharpCode.Decompiler/ILAst/ILCodes.cs | 2 + ICSharpCode.Decompiler/ILAst/Pattern.cs | 17 +- .../ILAst/YieldReturnDecompiler.cs | 280 +++++++++++++++++- ILSpy/ILAstLanguage.cs | 3 +- 7 files changed, 305 insertions(+), 24 deletions(-) diff --git a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs index a143d8e73..9f01c882b 100644 --- a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs +++ b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs @@ -63,7 +63,8 @@ namespace ICSharpCode.Decompiler.Ast bodyGraph.Optimize(context, ilMethod); context.CancellationToken.ThrowIfCancellationRequested(); - NameVariables.AssignNamesToVariables(methodDef.Parameters.Select(p => p.Name), astBuilder.Variables, ilMethod); + var allVariables = ilMethod.GetSelfAndChildrenRecursive().Select(e => e.Operand as ILVariable).Where(v => v != null && !v.IsGenerated).Distinct(); + NameVariables.AssignNamesToVariables(methodDef.Parameters.Select(p => p.Name), allVariables, ilMethod); context.CancellationToken.ThrowIfCancellationRequested(); Ast.BlockStatement astBlock = TransformBlock(ilMethod); @@ -555,6 +556,10 @@ namespace ICSharpCode.Decompiler.Ast case ILCode.Throw: return new Ast.ThrowStatement { Expression = arg1 }; case ILCode.Unaligned: return InlineAssembly(byteCode, args); case ILCode.Volatile: return InlineAssembly(byteCode, args); + case ILCode.YieldBreak: + return new Ast.YieldBreakStatement(); + case ILCode.YieldReturn: + return new Ast.YieldStatement { Expression = arg1 }; default: throw new Exception("Unknown OpCode: " + byteCode.Code); } } diff --git a/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs b/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs index b08f11fa1..8a060384c 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs @@ -207,8 +207,6 @@ namespace ICSharpCode.Decompiler.ILAst // Virtual instructions to load exception on stack Dictionary ldexceptions = new Dictionary(); - public List Variables; - public List Build(MethodDefinition methodDef, bool optimize) { this.methodDef = methodDef; @@ -449,7 +447,6 @@ namespace ICSharpCode.Decompiler.ILAst { if (optimize) { int varCount = methodDef.Body.Variables.Count; - this.Variables = new List(varCount * 2); for(int variableIndex = 0; variableIndex < varCount; variableIndex++) { // Find all stores and loads for this variable @@ -521,16 +518,13 @@ namespace ICSharpCode.Decompiler.ILAst load.Operand = newVar.Variable; } } - - // Record new variables to global list - this.Variables.AddRange(newVars.Select(v => v.Variable)); } } else { - this.Variables = methodDef.Body.Variables.Select(v => new ILVariable() { Name = string.IsNullOrEmpty(v.Name) ? "var_" + v.Index : v.Name, Type = v.VariableType, OriginalVariable = v }).ToList(); + var variables = methodDef.Body.Variables.Select(v => new ILVariable() { Name = string.IsNullOrEmpty(v.Name) ? "var_" + v.Index : v.Name, Type = v.VariableType, OriginalVariable = v }).ToList(); foreach(ByteCode byteCode in body) { if (byteCode.Code == ILCode.Ldloc || byteCode.Code == ILCode.Stloc || byteCode.Code == ILCode.Ldloca) { int index = ((VariableDefinition)byteCode.Operand).Index; - byteCode.Operand = this.Variables[index]; + byteCode.Operand = variables[index]; } } } @@ -704,9 +698,6 @@ namespace ICSharpCode.Decompiler.ILAst currExpr.Arguments[0].ILRanges.AddRange(currExpr.ILRanges); currExpr.Arguments[0].ILRanges.AddRange(nextExpr.Arguments[j].ILRanges); - // Remove from global list, if present - this.Variables.Remove((ILVariable)arg.Operand); - 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/ILAstTypes.cs b/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs index dae74b272..bd69d6fd0 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs @@ -363,6 +363,13 @@ namespace ICSharpCode.Decompiler.ILAst if (Operand != null) { if (Operand is ILLabel) { output.WriteReference(((ILLabel)Operand).Name, Operand); + } else if (Operand is ILLabel[]) { + ILLabel[] labels = (ILLabel[])Operand; + for (int i = 0; i < labels.Length; i++) { + if (i > 0) + output.Write(", "); + output.WriteReference(labels[i].Name, labels[i]); + } } else if (Operand is MethodReference) { MethodReference method = (MethodReference)Operand; method.DeclaringType.WriteTo(output, true, true); diff --git a/ICSharpCode.Decompiler/ILAst/ILCodes.cs b/ICSharpCode.Decompiler/ILAst/ILCodes.cs index 1d3201d3b..752045f33 100644 --- a/ICSharpCode.Decompiler/ILAst/ILCodes.cs +++ b/ICSharpCode.Decompiler/ILAst/ILCodes.cs @@ -263,6 +263,8 @@ namespace ICSharpCode.Decompiler.ILAst LoopBreak, LoopContinue, Ldc_Decimal, + YieldBreak, + YieldReturn, Pattern // used for ILAst pattern nodes } diff --git a/ICSharpCode.Decompiler/ILAst/Pattern.cs b/ICSharpCode.Decompiler/ILAst/Pattern.cs index 72774823f..0706f6620 100644 --- a/ICSharpCode.Decompiler/ILAst/Pattern.cs +++ b/ICSharpCode.Decompiler/ILAst/Pattern.cs @@ -45,7 +45,7 @@ namespace ICSharpCode.Decompiler.ILAst public class LoadFromVariable : ILExpression { - IVariablePattern v; + readonly IVariablePattern v; public LoadFromVariable(IVariablePattern v) : base(ILCode.Pattern, null) { @@ -59,6 +59,21 @@ namespace ICSharpCode.Decompiler.ILAst } } + public class LoadFromThis : ILExpression + { + public static readonly LoadFromThis Instance = new LoadFromThis(); + + public LoadFromThis() : base(ILCode.Pattern, null) + { + } + + public override bool Match(ILNode other) + { + ILExpression expr = other as ILExpression; + return expr != null && expr.Code == ILCode.Ldarg && ((ParameterDefinition)expr.Operand).Index < 0; + } + } + public class AnyILExpression : ILExpression { public ILExpression LastMatch; diff --git a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs index c2d61c0ca..0027c1633 100644 --- a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs +++ b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs @@ -28,8 +28,11 @@ namespace ICSharpCode.Decompiler.ILAst DecompilerContext context; TypeDefinition enumeratorType; MethodDefinition enumeratorCtor; + MethodDefinition disposeMethod; FieldDefinition stateField; + FieldDefinition currentField; Dictionary parameterToFieldMap; + List newBody; #region Run() method public static void Run(DecompilerContext context, ILBlock method) @@ -45,7 +48,12 @@ namespace ICSharpCode.Decompiler.ILAst try { #endif yrd.AnalyzeCtor(); + yrd.AnalyzeCurrentProperty(); yrd.ConstructExceptionTable(); + yrd.AnalyzeMoveNext(); + method.Body.Clear(); + method.EntryGoto = null; + method.Body.AddRange(yrd.newBody); #if !DEBUG } catch (YieldAnalysisFailedException) { return; @@ -97,12 +105,8 @@ namespace ICSharpCode.Decompiler.ILAst // the compiler might skip the above instruction in release builds; in that case, it directly returns stloc.Operand stloc2 = stloc; } - ILExpression br; - if (i + 1 < method.Body.Count && method.Body[i].Match(ILCode.Br, out br)) { - if (br.Operand != method.Body[i + 1]) - return false; - i += 2; - } + if (!SkipDummyBr(method, ref i)) + return false; if (i < method.Body.Count && method.Body[i].Match(ILCode.Ret, out ret)) { if (ret.Arguments[0].Code == ILCode.Ldloc && ret.Arguments[0].Operand == stloc2.Operand) { return true; @@ -111,6 +115,17 @@ namespace ICSharpCode.Decompiler.ILAst return false; } + bool SkipDummyBr(ILBlock method, ref int i) + { + ILExpression br; + if (i + 1 < method.Body.Count && method.Body[i].Match(ILCode.Br, out br)) { + if (br.Operand != method.Body[i + 1]) + return false; + i += 2; + } + return true; + } + bool MatchEnumeratorCreationNewObj(ILExpression expr, out MethodDefinition ctor) { // newobj(CurrentType/...::.ctor, ldc.i4(-2)) @@ -121,11 +136,20 @@ namespace ICSharpCode.Decompiler.ILAst return false; if (ctor == null || !ctor.DeclaringType.IsCompilerGenerated()) return false; - return ctor.DeclaringType.DeclaringType == context.CurrentType; + if (ctor.DeclaringType.DeclaringType != context.CurrentType) + return false; + foreach (TypeReference i in ctor.DeclaringType.Interfaces) { + if (i.Namespace == "System.Collections" && i.Name == "IEnumerator") + return true; + } + return false; } #endregion - #region Figure out what the state field is + #region Figure out what the 'state' field is + /// + /// Looks at the enumerator's ctor and figures out which of the fields holds the state. + /// void AnalyzeCtor() { ILBlock method = CreateILAst(enumeratorCtor); @@ -142,6 +166,9 @@ namespace ICSharpCode.Decompiler.ILAst throw new YieldAnalysisFailedException(); } + /// + /// Creates ILAst for the specified method, optimized up to before the 'YieldReturn' step. + /// ILBlock CreateILAst(MethodDefinition method) { if (method == null || !method.HasBody) @@ -156,6 +183,39 @@ namespace ICSharpCode.Decompiler.ILAst } #endregion + #region Figure out what the 'current' field is + static readonly ILExpression returnFieldFromThisPattern = new ILExpression(ILCode.Ret, null, new ILExpression(ILCode.Ldfld, ILExpression.AnyOperand, LoadFromThis.Instance)); + + /// + /// Looks at the enumerator's get_Current method and figures out which of the fields holds the current value. + /// + void AnalyzeCurrentProperty() + { + MethodDefinition getCurrentMethod = enumeratorType.Methods.FirstOrDefault( + m => m.Name.StartsWith("System.Collections.Generic.IEnumerator", StringComparison.Ordinal) + && m.Name.EndsWith(".get_Current", StringComparison.Ordinal)); + ILBlock method = CreateILAst(getCurrentMethod); + if (method.Body.Count == 1) { + // release builds directly return the current field + if (returnFieldFromThisPattern.Match(method.Body[0])) { + currentField = ((ILExpression)method.Body[0]).Arguments[0].Operand as FieldDefinition; + } + } else { + StoreToVariable v = new StoreToVariable(new ILExpression(ILCode.Ldfld, ILExpression.AnyOperand, LoadFromThis.Instance)); + if (v.Match(method.Body[0])) { + int i = 1; + if (SkipDummyBr(method, ref i) && i == method.Body.Count - 1) { + if (new ILExpression(ILCode.Ret, null, new LoadFromVariable(v)).Match(method.Body[i])) { + currentField = ((ILExpression)method.Body[0]).Arguments[0].Operand as FieldDefinition; + } + } + } + } + if (currentField == null) + throw new YieldAnalysisFailedException(); + } + #endregion + #region Construction of the exception table // We construct the exception table by analyzing the enumerator's Dispose() method. @@ -259,7 +319,7 @@ namespace ICSharpCode.Decompiler.ILAst void ConstructExceptionTable() { - MethodDefinition disposeMethod = enumeratorType.Methods.FirstOrDefault(m => m.Name == "System.IDisposable.Dispose"); + disposeMethod = enumeratorType.Methods.FirstOrDefault(m => m.Name == "System.IDisposable.Dispose"); ILBlock ilMethod = CreateILAst(disposeMethod); ranges = new DefaultDictionary(node => new StateRange()); @@ -396,5 +456,207 @@ namespace ICSharpCode.Decompiler.ILAst } #endregion #endregion + + #region Analysis of MoveNext() + ILVariable returnVariable; + ILLabel returnLabel; + ILLabel returnFalseLabel; + + void AnalyzeMoveNext() + { + MethodDefinition moveNextMethod = enumeratorType.Methods.FirstOrDefault(m => m.Name == "MoveNext"); + ILBlock ilMethod = CreateILAst(moveNextMethod); + + if (ilMethod.Body.Count == 0) + throw new YieldAnalysisFailedException(); + ILExpression lastReturn; + if (!ilMethod.Body.Last().Match(ILCode.Ret, out lastReturn)) + throw new YieldAnalysisFailedException(); + + ilMethod.Body.RemoveAll(n => n.Match(ILCode.Nop)); // remove nops + + // There are two possibilities: + if (lastReturn.Arguments[0].Code == ILCode.Ldloc) { + // a) the compiler uses a variable for returns (in debug builds, or when there are try-finally blocks) + returnVariable = (ILVariable)lastReturn.Arguments[0].Operand; + returnLabel = ilMethod.Body.ElementAtOrDefault(ilMethod.Body.Count - 2) as ILLabel; + if (returnLabel == null) + throw new YieldAnalysisFailedException(); + } else { + // b) the compiler directly returns constants + returnVariable = null; + returnLabel = null; + // In this case, the last return must return false. + if (lastReturn.Arguments[0].Code != ILCode.Ldc_I4 || (int)lastReturn.Arguments[0].Operand != 0) + throw new YieldAnalysisFailedException(); + } + + ILTryCatchBlock tryFaultBlock = ilMethod.Body[0] as ILTryCatchBlock; + List body; + int bodyLength; + if (tryFaultBlock != null) { + // there are try-finally blocks + if (returnVariable == null) // in this case, we must use a return variable + throw new YieldAnalysisFailedException(); + // must be a try-fault block: + if (tryFaultBlock.CatchBlocks.Count != 0 || tryFaultBlock.FinallyBlock != null || tryFaultBlock.FaultBlock == null) + throw new YieldAnalysisFailedException(); + + VerifyFaultBlock(tryFaultBlock.FaultBlock); + + body = tryFaultBlock.TryBlock.Body; + body.RemoveAll(n => n.Match(ILCode.Nop)); // remove nops + bodyLength = body.Count; + } else { + // no try-finally blocks + body = ilMethod.Body; + if (returnVariable == null) + bodyLength = body.Count - 1; // all except for the return statement + else + bodyLength = body.Count - 2; // all except for the return label and statement + } + + // Now verify that the last instruction in the body is 'ret(false)' + if (returnVariable != null) { + // If we don't have a return variable, we already verified that above. + if (bodyLength < 2) + throw new YieldAnalysisFailedException(); + ILExpression leave = body[bodyLength - 1] as ILExpression; + if (leave == null || leave.Operand != returnLabel || !(leave.Code == ILCode.Br || leave.Code == ILCode.Leave)) + throw new YieldAnalysisFailedException(); + ILExpression store0 = body[bodyLength - 2] as ILExpression; + if (store0 == null || store0.Code != ILCode.Stloc || store0.Operand != returnVariable) + throw new YieldAnalysisFailedException(); + if (store0.Arguments[0].Code != ILCode.Ldc_I4 || (int)store0.Arguments[0].Operand != 0) + throw new YieldAnalysisFailedException(); + + bodyLength -= 2; // don't conside the 'ret(false)' part of the body + } + returnFalseLabel = body.ElementAtOrDefault(--bodyLength) as ILLabel; + if (returnFalseLabel == null || bodyLength < 2) + throw new YieldAnalysisFailedException(); + + // Verify that the first instruction is a switch on this.state, and that the 2nd instruction is br + if (!new ILExpression(ILCode.Switch, ILExpression.AnyOperand, new ILExpression(ILCode.Ldfld, stateField, LoadFromThis.Instance)).Match(body[0])) + throw new YieldAnalysisFailedException(); + + if (!body[1].Match(ILCode.Br)) + throw new YieldAnalysisFailedException(); + + SimplifySwitch(body, ref bodyLength); + + // verify that the br (if no state is matched) leads to 'return false' + if (((ILExpression)body[1]).Operand != returnFalseLabel) + throw new YieldAnalysisFailedException(); + + ConvertBody(body, bodyLength); + } + + /// + /// Ensure the fault block contains the call to Dispose(). + /// + void VerifyFaultBlock(ILBlock faultBlock) + { + ILExpression call; + if (!(faultBlock.Body.Count == 2 || faultBlock.Body.Count == 3)) + throw new YieldAnalysisFailedException(); + if (!faultBlock.Body[0].Match(ILCode.Call, out call) && call.Operand == disposeMethod + && call.Arguments.Count == 1 && LoadFromThis.Instance.Match(call.Arguments[0])) + throw new YieldAnalysisFailedException(); + if (faultBlock.Body.Count == 3 && !faultBlock.Body[1].Match(ILCode.Nop)) + throw new YieldAnalysisFailedException(); + if (!faultBlock.Body[faultBlock.Body.Count - 1].Match(ILCode.Endfinally)) + throw new YieldAnalysisFailedException(); + } + + /// + /// Simplifies the switch statement at body[0], and the branch at body[1]. + /// + void SimplifySwitch(List body, ref int bodyLength) + { + HashSet regularLabels = new HashSet(); + Dictionary simplications = new Dictionary(); + for (int i = 2; i < bodyLength; i++) { + ILExpression expr = body[i] as ILExpression; + if (expr != null && expr.Operand is ILLabel) + regularLabels.Add((ILLabel)expr.Operand); + } + for (int i = 2; i + 1 < bodyLength; i += 2) { + ILLabel label = body[i] as ILLabel; + if (label == null || regularLabels.Contains(label)) + break; + ILExpression expr = body[i + 1] as ILExpression; + if (expr != null && expr.Code == ILCode.Br) { + simplications.Add(label, (ILLabel)expr.Operand); + } else { + break; + } + } + ILExpression switchExpr = (ILExpression)body[0]; + ILExpression brExpr = (ILExpression)body[1]; + Debug.Assert(switchExpr.Code == ILCode.Switch); + Debug.Assert(brExpr.Code == ILCode.Br); + ILLabel targetLabel; + if (simplications.TryGetValue((ILLabel)brExpr.Operand, out targetLabel)) + brExpr.Operand = targetLabel; + ILLabel[] labels = (ILLabel[])switchExpr.Operand; + for (int i = 0; i < labels.Length; i++) { + if (simplications.TryGetValue(labels[i], out targetLabel)) + labels[i] = targetLabel; + } + // remove the labels that aren't used anymore + body.RemoveRange(2, simplications.Count * 2); + bodyLength -= simplications.Count * 2; + } + + void ConvertBody(List body, int bodyLength) + { + newBody = new List(); + ILLabel[] switchLabels = (ILLabel[])((ILExpression)body[0]).Operand; + newBody.Add(MakeGoTo(switchLabels[0])); + int currentState = -1; + for (int pos = 2; pos < bodyLength; pos++) { + ILExpression expr = body[pos] as ILExpression; + if (expr != null && expr.Code == ILCode.Stfld && LoadFromThis.Instance.Match(expr.Arguments[0])) { + if (expr.Operand == stateField) { + if (expr.Arguments[1].Code != ILCode.Ldc_I4) + throw new YieldAnalysisFailedException(); + currentState = (int)expr.Arguments[1].Operand; + } else if (expr.Operand == currentField) { + newBody.Add(new ILExpression(ILCode.YieldReturn, null, expr.Arguments[1])); + } else { + newBody.Add(body[pos]); + // TODO convert field to local + } + } else if (returnVariable != null && expr != null && expr.Code == ILCode.Stloc && expr.Operand == returnVariable) { + ILExpression br = body.ElementAtOrDefault(++pos) as ILExpression; + if (br == null || !(br.Code == ILCode.Br || br.Code == ILCode.Leave) || br.Operand != returnLabel || expr.Arguments[0].Code != ILCode.Ldc_I4) + throw new YieldAnalysisFailedException(); + int val = (int)expr.Arguments[0].Operand; + if (val == 0) { + newBody.Add(new ILExpression(ILCode.YieldBreak, null)); + } else if (val == 1) { + if (currentState >= 0 && currentState < switchLabels.Length) + newBody.Add(MakeGoTo(switchLabels[currentState])); + else + newBody.Add(new ILExpression(ILCode.YieldBreak, null)); + } else { + throw new YieldAnalysisFailedException(); + } + } else { + newBody.Add(body[pos]); + } + } + newBody.Add(returnFalseLabel); + } + + ILExpression MakeGoTo(ILLabel targetLabel) + { + if (targetLabel == returnFalseLabel) + return new ILExpression(ILCode.YieldBreak, null); + else + return new ILExpression(ILCode.Br, targetLabel); + } + #endregion } } diff --git a/ILSpy/ILAstLanguage.cs b/ILSpy/ILAstLanguage.cs index 1db27d100..fc6217a48 100644 --- a/ILSpy/ILAstLanguage.cs +++ b/ILSpy/ILAstLanguage.cs @@ -60,8 +60,7 @@ namespace ICSharpCode.ILSpy new ILAstOptimizer().Optimize(context, ilMethod, abortBeforeStep.Value); } - var allVariables = astBuilder.Variables - .Concat(ilMethod.GetSelfAndChildrenRecursive().Select(e => e.Operand as ILVariable).Where(v => v != null)).Distinct(); + var allVariables = ilMethod.GetSelfAndChildrenRecursive().Select(e => e.Operand as ILVariable).Where(v => v != null).Distinct(); foreach (ILVariable v in allVariables) { output.WriteDefinition(v.Name, v); if (v.Type != null) { From 6da92cd8d1e44ea4e6cf959f2a2b606bafe01ef2 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 6 Mar 2011 07:26:48 +0100 Subject: [PATCH 04/15] yield return decompilation: translate fields to local variables --- .../ILAst/ILAstOptimizer.cs | 8 +- ICSharpCode.Decompiler/ILAst/ILCodes.cs | 1 + .../ILAst/YieldReturnDecompiler.cs | 110 ++++++++++++++++-- 3 files changed, 106 insertions(+), 13 deletions(-) diff --git a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs index fa60a5e1d..15654c78f 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs @@ -179,7 +179,7 @@ namespace ICSharpCode.Decompiler.ILAst lastBlock.FallthoughGoto = new ILExpression(ILCode.Br, basicBlock.EntryLabel); } } else { - basicBlock.Body.Add(currNode); + basicBlock.Body.Add(currNode); } } } @@ -229,7 +229,7 @@ namespace ICSharpCode.Decompiler.ILAst if (TrySimplifyShortCircuit(block.Body, bb)) { modified = true; continue; - } + } if (TrySimplifyTernaryOperator(block.Body, bb)) { modified = true; continue; @@ -476,7 +476,7 @@ namespace ICSharpCode.Decompiler.ILAst labelToCfNode.TryGetValue(trueLabel, out trueTarget); ControlFlowNode falseTarget; labelToCfNode.TryGetValue(falseLabel, out falseTarget); - + // If one point inside the loop and the other outside if ((!loopContents.Contains(trueTarget) && loopContents.Contains(falseTarget)) || (loopContents.Contains(trueTarget) && !loopContents.Contains(falseTarget)) ) @@ -859,6 +859,7 @@ namespace ICSharpCode.Decompiler.ILAst public static bool CanFallthough(this ILNode node) { + // TODO: similar to ILCodes.CanFallThough, but handles slightly different cases?? ILExpression expr = node as ILExpression; if (expr != null) { switch(expr.Code) { @@ -868,6 +869,7 @@ namespace ICSharpCode.Decompiler.ILAst case ILCode.Rethrow: case ILCode.LoopContinue: case ILCode.LoopBreak: + case ILCode.YieldBreak: return false; } } diff --git a/ICSharpCode.Decompiler/ILAst/ILCodes.cs b/ICSharpCode.Decompiler/ILAst/ILCodes.cs index 752045f33..e7012a9a6 100644 --- a/ICSharpCode.Decompiler/ILAst/ILCodes.cs +++ b/ICSharpCode.Decompiler/ILAst/ILCodes.cs @@ -288,6 +288,7 @@ namespace ICSharpCode.Decompiler.ILAst case ILCode.Endfinally: case ILCode.Throw: case ILCode.Rethrow: + case ILCode.YieldBreak: return false; default: return true; diff --git a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs index 0027c1633..f05655d6c 100644 --- a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs +++ b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs @@ -31,7 +31,7 @@ namespace ICSharpCode.Decompiler.ILAst MethodDefinition disposeMethod; FieldDefinition stateField; FieldDefinition currentField; - Dictionary parameterToFieldMap; + Dictionary fieldToParameterMap; List newBody; #region Run() method @@ -49,16 +49,18 @@ namespace ICSharpCode.Decompiler.ILAst #endif yrd.AnalyzeCtor(); yrd.AnalyzeCurrentProperty(); + yrd.ResolveIEnumerableIEnumeratorFieldMapping(); yrd.ConstructExceptionTable(); yrd.AnalyzeMoveNext(); - method.Body.Clear(); - method.EntryGoto = null; - method.Body.AddRange(yrd.newBody); + yrd.TranslateFieldsToLocalAccess(); #if !DEBUG } catch (YieldAnalysisFailedException) { return; } #endif + method.Body.Clear(); + method.EntryGoto = null; + method.Body.AddRange(yrd.newBody); } #endregion @@ -82,7 +84,7 @@ namespace ICSharpCode.Decompiler.ILAst if (!MatchEnumeratorCreationNewObj(stloc.Arguments[0], out enumeratorCtor)) return false; - parameterToFieldMap = new Dictionary(); + fieldToParameterMap = new Dictionary(); int i = 1; ILExpression stfld; while (i < method.Body.Count && method.Body[i].Match(ILCode.Stfld, out stfld)) { @@ -92,7 +94,7 @@ namespace ICSharpCode.Decompiler.ILAst return false; if (ldloc.Operand != stloc.Operand || !(stfld.Operand is FieldDefinition)) return false; - parameterToFieldMap[(ParameterDefinition)ldarg.Operand] = (FieldDefinition)stfld.Operand; + fieldToParameterMap[(FieldDefinition)stfld.Operand] = (ParameterDefinition)ldarg.Operand; i++; } ILExpression stloc2; @@ -216,6 +218,35 @@ namespace ICSharpCode.Decompiler.ILAst } #endregion + #region Figure out the mapping of IEnumerable fields to IEnumerator fields + void ResolveIEnumerableIEnumeratorFieldMapping() + { + MethodDefinition getEnumeratorMethod = enumeratorType.Methods.FirstOrDefault( + m => m.Name.StartsWith("System.Collections.Generic.IEnumerable", StringComparison.Ordinal) + && m.Name.EndsWith(".GetEnumerator", StringComparison.Ordinal)); + if (getEnumeratorMethod == null) + return; // no mappings (maybe it's just an IEnumerator implementation?) + + ILExpression mappingPattern = new ILExpression( + ILCode.Stfld, ILExpression.AnyOperand, new AnyILExpression(), + new ILExpression(ILCode.Ldfld, ILExpression.AnyOperand, LoadFromThis.Instance)); + + ILBlock method = CreateILAst(getEnumeratorMethod); + foreach (ILNode node in method.Body) { + if (mappingPattern.Match(node)) { + ILExpression stfld = (ILExpression)node; + FieldDefinition storedField = stfld.Operand as FieldDefinition; + FieldDefinition loadedField = stfld.Arguments[1].Operand as FieldDefinition; + if (storedField != null && loadedField != null) { + ParameterDefinition mappedParameter; + if (fieldToParameterMap.TryGetValue(loadedField, out mappedParameter)) + fieldToParameterMap[storedField] = mappedParameter; + } + } + } + } + #endregion + #region Construction of the exception table // We construct the exception table by analyzing the enumerator's Dispose() method. @@ -457,7 +488,7 @@ namespace ICSharpCode.Decompiler.ILAst #endregion #endregion - #region Analysis of MoveNext() + #region Analysis and Transformation of MoveNext() ILVariable returnVariable; ILLabel returnLabel; ILLabel returnFalseLabel; @@ -615,9 +646,11 @@ namespace ICSharpCode.Decompiler.ILAst ILLabel[] switchLabels = (ILLabel[])((ILExpression)body[0]).Operand; newBody.Add(MakeGoTo(switchLabels[0])); int currentState = -1; + // Copy all instructions from the old body to newBody. for (int pos = 2; pos < bodyLength; pos++) { ILExpression expr = body[pos] as ILExpression; if (expr != null && expr.Code == ILCode.Stfld && LoadFromThis.Instance.Match(expr.Arguments[0])) { + // Handle stores to 'state' or 'current' if (expr.Operand == stateField) { if (expr.Arguments[1].Code != ILCode.Ldc_I4) throw new YieldAnalysisFailedException(); @@ -626,28 +659,35 @@ namespace ICSharpCode.Decompiler.ILAst newBody.Add(new ILExpression(ILCode.YieldReturn, null, expr.Arguments[1])); } else { newBody.Add(body[pos]); - // TODO convert field to local } } else if (returnVariable != null && expr != null && expr.Code == ILCode.Stloc && expr.Operand == returnVariable) { + // handle store+branch to the returnVariable ILExpression br = body.ElementAtOrDefault(++pos) as ILExpression; if (br == null || !(br.Code == ILCode.Br || br.Code == ILCode.Leave) || br.Operand != returnLabel || expr.Arguments[0].Code != ILCode.Ldc_I4) throw new YieldAnalysisFailedException(); int val = (int)expr.Arguments[0].Operand; if (val == 0) { - newBody.Add(new ILExpression(ILCode.YieldBreak, null)); + newBody.Add(MakeGoTo(returnFalseLabel)); } else if (val == 1) { if (currentState >= 0 && currentState < switchLabels.Length) newBody.Add(MakeGoTo(switchLabels[currentState])); else - newBody.Add(new ILExpression(ILCode.YieldBreak, null)); + newBody.Add(MakeGoTo(returnFalseLabel)); } else { throw new YieldAnalysisFailedException(); } + } else if (expr != null && expr.Code == ILCode.Call && expr.Operand == disposeMethod && expr.Arguments.Count == 1 && LoadFromThis.Instance.Match(expr.Arguments[0])) { + // Explicit call to dispose is used for "yield break;" within the method. + ILExpression br = body.ElementAtOrDefault(++pos) as ILExpression; + if (br == null || !(br.Code == ILCode.Br || br.Code == ILCode.Leave) || br.Operand != returnFalseLabel) + throw new YieldAnalysisFailedException(); + newBody.Add(MakeGoTo(returnFalseLabel)); } else { newBody.Add(body[pos]); } } newBody.Add(returnFalseLabel); + newBody.Add(new ILExpression(ILCode.YieldBreak, null)); } ILExpression MakeGoTo(ILLabel targetLabel) @@ -658,5 +698,55 @@ namespace ICSharpCode.Decompiler.ILAst return new ILExpression(ILCode.Br, targetLabel); } #endregion + + #region TranslateFieldsToLocalAccess + void TranslateFieldsToLocalAccess() + { + var fieldToLocalMap = new DefaultDictionary(f => new ILVariable { Name = f.Name, Type = f.FieldType }); + foreach (ILNode node in newBody) { + foreach (ILExpression expr in node.GetSelfAndChildrenRecursive()) { + FieldDefinition field = expr.Operand as FieldDefinition; + if (field != null) { + switch (expr.Code) { + case ILCode.Ldfld: + if (LoadFromThis.Instance.Match(expr.Arguments[0])) { + if (fieldToParameterMap.ContainsKey(field)) { + expr.Code = ILCode.Ldarg; + expr.Operand = fieldToParameterMap[field]; + } else { + expr.Code = ILCode.Ldloc; + expr.Operand = fieldToLocalMap[field]; + } + expr.Arguments.Clear(); + } + break; + case ILCode.Stfld: + if (LoadFromThis.Instance.Match(expr.Arguments[0])) { + if (fieldToParameterMap.ContainsKey(field)) { + expr.Code = ILCode.Starg; + expr.Operand = fieldToParameterMap[field]; + } else { + expr.Code = ILCode.Stloc; + expr.Operand = fieldToLocalMap[field]; + } + expr.Arguments.RemoveAt(0); + } + break; + case ILCode.Ldflda: + if (fieldToParameterMap.ContainsKey(field)) { + expr.Code = ILCode.Ldarga; + expr.Operand = fieldToParameterMap[field]; + } else { + expr.Code = ILCode.Ldloca; + expr.Operand = fieldToLocalMap[field]; + } + expr.Arguments.Clear(); + break; + } + } + } + } + } + #endregion } } From 5ca3ddb860bccd39fd92d678475e10875befc9d4 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 6 Mar 2011 07:50:30 +0100 Subject: [PATCH 05/15] Yield return decompiler: reconstruct try-finally blocks. --- .../ILAst/YieldReturnDecompiler.cs | 70 +++++++++++++++++-- 1 file changed, 64 insertions(+), 6 deletions(-) diff --git a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs index f05655d6c..195c1d6cb 100644 --- a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs +++ b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs @@ -347,6 +347,7 @@ namespace ICSharpCode.Decompiler.ILAst #endregion DefaultDictionary ranges; + Dictionary finallyMethodToStateInterval = new Dictionary(); void ConstructExceptionTable() { @@ -373,8 +374,14 @@ namespace ICSharpCode.Decompiler.ILAst if (!finallyBody[finallyBody.Count - 1].Match(ILCode.Endfinally)) throw new YieldAnalysisFailedException(); - Debug.WriteLine("State " + interval + " -> " + call.Operand.ToString()); + MethodDefinition mdef = call.Operand as MethodDefinition; + if (mdef != null) { + if (finallyMethodToStateInterval.ContainsKey(mdef)) + throw new YieldAnalysisFailedException(); + finallyMethodToStateInterval.Add(mdef, interval); + } } + ranges = null; } #region Assign StateRanges / Symbolic Execution @@ -640,11 +647,24 @@ namespace ICSharpCode.Decompiler.ILAst bodyLength -= simplications.Count * 2; } + struct SetState + { + public readonly int NewBodyPos; + public readonly int NewState; + + public SetState(int newBodyPos, int newState) + { + this.NewBodyPos = newBodyPos; + this.NewState = newState; + } + } + void ConvertBody(List body, int bodyLength) { newBody = new List(); ILLabel[] switchLabels = (ILLabel[])((ILExpression)body[0]).Operand; newBody.Add(MakeGoTo(switchLabels[0])); + List stateChanges = new List(); int currentState = -1; // Copy all instructions from the old body to newBody. for (int pos = 2; pos < bodyLength; pos++) { @@ -655,6 +675,7 @@ namespace ICSharpCode.Decompiler.ILAst if (expr.Arguments[1].Code != ILCode.Ldc_I4) throw new YieldAnalysisFailedException(); currentState = (int)expr.Arguments[1].Operand; + stateChanges.Add(new SetState(newBody.Count, currentState)); } else if (expr.Operand == currentField) { newBody.Add(new ILExpression(ILCode.YieldReturn, null, expr.Arguments[1])); } else { @@ -676,12 +697,31 @@ namespace ICSharpCode.Decompiler.ILAst } else { throw new YieldAnalysisFailedException(); } - } else if (expr != null && expr.Code == ILCode.Call && expr.Operand == disposeMethod && expr.Arguments.Count == 1 && LoadFromThis.Instance.Match(expr.Arguments[0])) { - // Explicit call to dispose is used for "yield break;" within the method. - ILExpression br = body.ElementAtOrDefault(++pos) as ILExpression; - if (br == null || !(br.Code == ILCode.Br || br.Code == ILCode.Leave) || br.Operand != returnFalseLabel) + } else if (expr != null && expr.Code == ILCode.Call && expr.Arguments.Count == 1 && LoadFromThis.Instance.Match(expr.Arguments[0])) { + MethodDefinition method = expr.Operand as MethodDefinition; + if (method == null) throw new YieldAnalysisFailedException(); - newBody.Add(MakeGoTo(returnFalseLabel)); + Interval interval; + if (method == disposeMethod) { + // Explicit call to dispose is used for "yield break;" within the method. + ILExpression br = body.ElementAtOrDefault(++pos) as ILExpression; + if (br == null || !(br.Code == ILCode.Br || br.Code == ILCode.Leave) || br.Operand != returnFalseLabel) + throw new YieldAnalysisFailedException(); + newBody.Add(MakeGoTo(returnFalseLabel)); + } else if (finallyMethodToStateInterval.TryGetValue(method, out interval)) { + // Call to Finally-method + int index = stateChanges.FindIndex(ss => ss.NewState >= interval.Start && ss.NewState <= interval.End); + if (index < 0) + throw new YieldAnalysisFailedException(); + SetState stateChange = stateChanges[index]; + stateChanges.RemoveRange(index, stateChanges.Count - index); // remove all state changes up to the one we found + ILTryCatchBlock tryFinally = new ILTryCatchBlock(); + tryFinally.TryBlock = new ILBlock(newBody.GetRange(stateChange.NewBodyPos, newBody.Count - stateChange.NewBodyPos)); + newBody.RemoveRange(stateChange.NewBodyPos, newBody.Count - stateChange.NewBodyPos); // remove all nodes that we just moved into the try block + tryFinally.CatchBlocks = new List(); + tryFinally.FinallyBlock = ConvertFinallyBlock(method); + newBody.Add(tryFinally); + } } else { newBody.Add(body[pos]); } @@ -697,6 +737,24 @@ namespace ICSharpCode.Decompiler.ILAst else return new ILExpression(ILCode.Br, targetLabel); } + + ILBlock ConvertFinallyBlock(MethodDefinition finallyMethod) + { + ILBlock block = CreateILAst(finallyMethod); + block.Body.RemoveAll(n => n.Match(ILCode.Nop)); + // Get rid of assignment to state + ILExpression stfld; + if (block.Body.Count > 0 && block.Body[0].Match(ILCode.Stfld, out stfld)) { + if (stfld.Operand == stateField && LoadFromThis.Instance.Match(stfld.Arguments[0])) + block.Body.RemoveAt(0); + } + // Convert ret to endfinally + foreach (ILExpression expr in block.GetSelfAndChildrenRecursive()) { + if (expr.Code == ILCode.Ret) + expr.Code = ILCode.Endfinally; + } + return block; + } #endregion #region TranslateFieldsToLocalAccess From 29523a7de80f6087301b90addfa074339ba6f9e4 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 6 Mar 2011 16:20:20 +0100 Subject: [PATCH 06/15] Yield-return decompiler: Add leave instruction out of try block; fixes NullReferenceExecption when yield return is within using statement. --- ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs index 195c1d6cb..19dde1952 100644 --- a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs +++ b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs @@ -713,7 +713,13 @@ namespace ICSharpCode.Decompiler.ILAst int index = stateChanges.FindIndex(ss => ss.NewState >= interval.Start && ss.NewState <= interval.End); if (index < 0) throw new YieldAnalysisFailedException(); + + ILLabel label = new ILLabel(); + label.Name = "JumpOutOfTryFinally" + interval.Start + "_" + interval.End; + newBody.Add(new ILExpression(ILCode.Leave, label)); + SetState stateChange = stateChanges[index]; + // Move all instructions from stateChange.Pos to newBody.Count into a try-block stateChanges.RemoveRange(index, stateChanges.Count - index); // remove all state changes up to the one we found ILTryCatchBlock tryFinally = new ILTryCatchBlock(); tryFinally.TryBlock = new ILBlock(newBody.GetRange(stateChange.NewBodyPos, newBody.Count - stateChange.NewBodyPos)); @@ -721,6 +727,7 @@ namespace ICSharpCode.Decompiler.ILAst tryFinally.CatchBlocks = new List(); tryFinally.FinallyBlock = ConvertFinallyBlock(method); newBody.Add(tryFinally); + newBody.Add(label); } } else { newBody.Add(body[pos]); From b2b93aff71a3713d058ebd36368aa2b677269b63 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 6 Mar 2011 16:27:45 +0100 Subject: [PATCH 07/15] Fix bugs when decompiling release builds of yield-return enumerators. --- .../ILAst/YieldReturnDecompiler.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs index 19dde1952..65a377bab 100644 --- a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs +++ b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs @@ -31,7 +31,7 @@ namespace ICSharpCode.Decompiler.ILAst MethodDefinition disposeMethod; FieldDefinition stateField; FieldDefinition currentField; - Dictionary fieldToParameterMap; + Dictionary fieldToParameterMap = new Dictionary(); List newBody; #region Run() method @@ -84,7 +84,6 @@ namespace ICSharpCode.Decompiler.ILAst if (!MatchEnumeratorCreationNewObj(stloc.Arguments[0], out enumeratorCtor)) return false; - fieldToParameterMap = new Dictionary(); int i = 1; ILExpression stfld; while (i < method.Body.Count && method.Body[i].Match(ILCode.Stfld, out stfld)) { @@ -697,6 +696,21 @@ namespace ICSharpCode.Decompiler.ILAst } else { throw new YieldAnalysisFailedException(); } + } else if (expr != null && expr.Code == ILCode.Ret) { + if (expr.Arguments.Count != 1 || expr.Arguments[0].Code != ILCode.Ldc_I4) + throw new YieldAnalysisFailedException(); + // handle direct return (e.g. in release builds) + int val = (int)expr.Arguments[0].Operand; + if (val == 0) { + newBody.Add(MakeGoTo(returnFalseLabel)); + } else if (val == 1) { + if (currentState >= 0 && currentState < switchLabels.Length) + newBody.Add(MakeGoTo(switchLabels[currentState])); + else + newBody.Add(MakeGoTo(returnFalseLabel)); + } else { + throw new YieldAnalysisFailedException(); + } } else if (expr != null && expr.Code == ILCode.Call && expr.Arguments.Count == 1 && LoadFromThis.Instance.Match(expr.Arguments[0])) { MethodDefinition method = expr.Operand as MethodDefinition; if (method == null) From 43706248fb236188304af27f460307750e2e48b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Srbeck=C3=BD?= Date: Sun, 6 Mar 2011 16:06:36 +0000 Subject: [PATCH 08/15] Fixed FindLoopContent - it sometimes pulled in more then just the natural loop --- ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs index 6aa3f352c..a65f935cb 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs @@ -742,8 +742,8 @@ namespace ICSharpCode.Decompiler.ILAst static HashSet FindLoopContent(HashSet scope, ControlFlowNode head) { - var exitNodes = head.DominanceFrontier.SelectMany(n => n.Predecessors); - HashSet agenda = new HashSet(exitNodes); + var viaBackEdges = head.Predecessors.Where(p => head.Dominates(p)); + HashSet agenda = new HashSet(viaBackEdges); HashSet result = new HashSet(); while(agenda.Count > 0) { From e71935144a7ca391dd6f23edc69ad8e1ae90bb7a Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 6 Mar 2011 17:09:57 +0100 Subject: [PATCH 09/15] Adjust yield return decompiler to David's pattern matching refactoring. --- .../ILAst/ILAstOptimizer.cs | 107 +++++++++++------- ICSharpCode.Decompiler/ILAst/Pattern.cs | 10 +- .../ILAst/YieldReturnDecompiler.cs | 95 ++++++++-------- 3 files changed, 120 insertions(+), 92 deletions(-) diff --git a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs index edd67c532..d53d833b8 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs @@ -110,17 +110,17 @@ namespace ICSharpCode.Decompiler.ILAst expr.Arguments.Single().ILRanges.AddRange(expr.ILRanges); expr.ILRanges.Clear(); continue; - case ILCode.__Brfalse: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, expr.Arguments.Single())); break; - case ILCode.__Beq: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Ceq, null, expr.Arguments)); break; - case ILCode.__Bne_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Ceq, null, expr.Arguments))); break; - case ILCode.__Bgt: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Cgt, null, expr.Arguments)); break; - case ILCode.__Bgt_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Cgt_Un, null, expr.Arguments)); break; - case ILCode.__Ble: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Cgt, null, expr.Arguments))); break; - case ILCode.__Ble_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Cgt_Un, null, expr.Arguments))); break; - case ILCode.__Blt: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Clt, null, expr.Arguments)); break; - case ILCode.__Blt_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Clt_Un, null, expr.Arguments)); break; - case ILCode.__Bge: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Clt, null, expr.Arguments))); break; - case ILCode.__Bge_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Clt_Un, null, expr.Arguments))); break; + case ILCode.__Brfalse: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, expr.Arguments.Single())); break; + case ILCode.__Beq: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Ceq, null, expr.Arguments)); break; + case ILCode.__Bne_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Ceq, null, expr.Arguments))); break; + case ILCode.__Bgt: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Cgt, null, expr.Arguments)); break; + case ILCode.__Bgt_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Cgt_Un, null, expr.Arguments)); break; + case ILCode.__Ble: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Cgt, null, expr.Arguments))); break; + case ILCode.__Ble_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Cgt_Un, null, expr.Arguments))); break; + case ILCode.__Blt: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Clt, null, expr.Arguments)); break; + case ILCode.__Blt_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Clt_Un, null, expr.Arguments)); break; + case ILCode.__Bge: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Clt, null, expr.Arguments))); break; + case ILCode.__Bge_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Clt_Un, null, expr.Arguments))); break; default: continue; } @@ -495,36 +495,36 @@ namespace ICSharpCode.Decompiler.ILAst // Use loop to implement the condition result.Add(new ILBasicBlock() { - EntryLabel = basicBlock.EntryLabel, - Body = new List() { - new ILWhileLoop() { - Condition = condExpr, - BodyBlock = new ILBlock() { - EntryGoto = new ILExpression(ILCode.Br, trueLabel), - Body = FindLoops(loopContents, node, true) - } - }, - new ILExpression(ILCode.Br, falseLabel) - }, - FallthoughGoto = null - }); + EntryLabel = basicBlock.EntryLabel, + Body = new List() { + new ILWhileLoop() { + Condition = condExpr, + BodyBlock = new ILBlock() { + EntryGoto = new ILExpression(ILCode.Br, trueLabel), + Body = FindLoops(loopContents, node, true) + } + }, + new ILExpression(ILCode.Br, falseLabel) + }, + FallthoughGoto = null + }); } } // Fallback method: while(true) if (scope.Contains(node)) { result.Add(new ILBasicBlock() { - EntryLabel = new ILLabel() { Name = "Loop_" + (nextLabelIndex++) }, - Body = new List() { - new ILWhileLoop() { - BodyBlock = new ILBlock() { - EntryGoto = new ILExpression(ILCode.Br, basicBlock.EntryLabel), - Body = FindLoops(loopContents, node, true) - } - }, - }, - FallthoughGoto = null - }); + EntryLabel = new ILLabel() { Name = "Loop_" + (nextLabelIndex++) }, + Body = new List() { + new ILWhileLoop() { + BodyBlock = new ILBlock() { + EntryGoto = new ILExpression(ILCode.Br, basicBlock.EntryLabel), + Body = FindLoops(loopContents, node, true) + } + }, + }, + FallthoughGoto = null + }); } // Move the content into loop block @@ -643,7 +643,7 @@ namespace ICSharpCode.Decompiler.ILAst var caseBlock = new ILSwitch.CaseBlock() { EntryGoto = new ILExpression(ILCode.Br, fallLabel) }; ilSwitch.CaseBlocks.Add(caseBlock); newBB.FallthoughGoto = null; - + scope.ExceptWith(content); caseBlock.Body.AddRange(FindConditions(content, fallTarget)); // Add explicit break which should not be used by default, but the goto removal might decide to use it @@ -671,9 +671,9 @@ namespace ICSharpCode.Decompiler.ILAst FalseBlock = new ILBlock() { EntryGoto = new ILExpression(ILCode.Br, falseLabel) } }; result.Add(new ILBasicBlock() { - EntryLabel = block.EntryLabel, // Keep the entry label - Body = { ilCond } - }); + EntryLabel = block.EntryLabel, // Keep the entry label + Body = { ilCond } + }); // Remove the item immediately so that it is not picked up as content scope.RemoveOrThrow(node); @@ -870,6 +870,17 @@ namespace ICSharpCode.Decompiler.ILAst return false; } + public static bool Match(this ILNode node, ILCode code, out ILExpression arg) + { + List args; + if (node.Match(code, out args) && args.Count == 1) { + arg = args[0]; + return true; + } + arg = null; + return false; + } + public static bool Match(this ILNode node, ILCode code, out T operand, out List args) { ILExpression expr = node as ILExpression; @@ -886,14 +897,27 @@ namespace ICSharpCode.Decompiler.ILAst public static bool Match(this ILNode node, ILCode code, out T operand, out ILExpression arg) { List args; - if (node.Match(code, out operand, out args)) { - arg = args.Single(); + if (node.Match(code, out operand, out args) && args.Count == 1) { + arg = args[0]; return true; } arg = null; return false; } + public static bool Match(this ILNode node, ILCode code, out T operand, out ILExpression arg1, out ILExpression arg2) + { + List args; + if (node.Match(code, out operand, out args) && args.Count == 2) { + arg1 = args[0]; + arg2 = args[1]; + return true; + } + arg1 = null; + arg2 = null; + return false; + } + public static bool Match(this ILBasicBlock bb, ILCode code, out T operand, out ILExpression arg, out ILLabel fallLabel) { if (bb.Body.Count == 1) { @@ -910,7 +934,6 @@ namespace ICSharpCode.Decompiler.ILAst public static bool CanFallThough(this ILNode node) { - // TODO: similar to ILCodes.CanFallThough, but handles slightly different cases?? ILExpression expr = node as ILExpression; if (expr != null) { return expr.Code.CanFallThough(); diff --git a/ICSharpCode.Decompiler/ILAst/Pattern.cs b/ICSharpCode.Decompiler/ILAst/Pattern.cs index 0706f6620..8fb8c866e 100644 --- a/ICSharpCode.Decompiler/ILAst/Pattern.cs +++ b/ICSharpCode.Decompiler/ILAst/Pattern.cs @@ -59,18 +59,20 @@ namespace ICSharpCode.Decompiler.ILAst } } - public class LoadFromThis : ILExpression + public class LoadFromArgument : ILExpression { - public static readonly LoadFromThis Instance = new LoadFromThis(); + int index; + public static readonly LoadFromArgument This = new LoadFromArgument(-1); - public LoadFromThis() : base(ILCode.Pattern, null) + public LoadFromArgument(int index) : base(ILCode.Pattern, null) { + this.index = index; } public override bool Match(ILNode other) { ILExpression expr = other as ILExpression; - return expr != null && expr.Code == ILCode.Ldarg && ((ParameterDefinition)expr.Operand).Index < 0; + return expr != null && expr.Code == ILCode.Ldarg && ((ParameterDefinition)expr.Operand).Index == index; } } diff --git a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs index 65a377bab..87bb342a2 100644 --- a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs +++ b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs @@ -69,47 +69,51 @@ namespace ICSharpCode.Decompiler.ILAst { if (method.Body.Count == 0) return false; - ILExpression ret; + ILExpression newObj; if (method.Body.Count == 1) { // ret(newobj(...)) - if (method.Body[0].Match(ILCode.Ret, out ret) && ret.Arguments.Count == 1) - return MatchEnumeratorCreationNewObj(ret.Arguments[0], out enumeratorCtor); + if (method.Body[0].Match(ILCode.Ret, out newObj)) + return MatchEnumeratorCreationNewObj(newObj, out enumeratorCtor); else return false; } // stloc(var_1, newobj(..) - ILExpression stloc; - if (!method.Body[0].Match(ILCode.Stloc, out stloc)) + ILVariable var1; + if (!method.Body[0].Match(ILCode.Stloc, out var1, out newObj)) return false; - if (!MatchEnumeratorCreationNewObj(stloc.Arguments[0], out enumeratorCtor)) + if (!MatchEnumeratorCreationNewObj(newObj, out enumeratorCtor)) return false; - int i = 1; - ILExpression stfld; - while (i < method.Body.Count && method.Body[i].Match(ILCode.Stfld, out stfld)) { + int i; + for (i = 1; i < method.Body.Count; i++) { // stfld(..., ldloc(var_1), ldarg(...)) + FieldReference storedField; ILExpression ldloc, ldarg; - if (!(stfld.Arguments[0].Match(ILCode.Ldloc, out ldloc) && stfld.Arguments[1].Match(ILCode.Ldarg, out ldarg))) + if (!method.Body[i].Match(ILCode.Stfld, out storedField, out ldloc, out ldarg)) + break; + if (ldloc.Code != ILCode.Ldloc || ldarg.Code != ILCode.Ldarg) return false; - if (ldloc.Operand != stloc.Operand || !(stfld.Operand is FieldDefinition)) + if (ldloc.Operand != storedField || !(storedField is FieldDefinition)) return false; - fieldToParameterMap[(FieldDefinition)stfld.Operand] = (ParameterDefinition)ldarg.Operand; - i++; + fieldToParameterMap[(FieldDefinition)storedField] = (ParameterDefinition)ldarg.Operand; } - ILExpression stloc2; - if (i < method.Body.Count && method.Body[i].Match(ILCode.Stloc, out stloc2)) { + ILVariable var2; + ILExpression ldlocForStloc2; + if (i < method.Body.Count && method.Body[i].Match(ILCode.Stloc, out var2, out ldlocForStloc2)) { // stloc(var_2, ldloc(var_1)) - if (stloc2.Arguments[0].Code != ILCode.Ldloc || stloc2.Arguments[0].Operand != stloc.Operand) + if (ldlocForStloc2.Code != ILCode.Ldloc || ldlocForStloc2.Operand != var1) return false; i++; } else { // the compiler might skip the above instruction in release builds; in that case, it directly returns stloc.Operand - stloc2 = stloc; + var2 = var1; } if (!SkipDummyBr(method, ref i)) return false; - if (i < method.Body.Count && method.Body[i].Match(ILCode.Ret, out ret)) { - if (ret.Arguments[0].Code == ILCode.Ldloc && ret.Arguments[0].Operand == stloc2.Operand) { + ILExpression retArg; + if (i < method.Body.Count && method.Body[i].Match(ILCode.Ret, out retArg)) { + // ret(ldloc(var_2)) + if (retArg.Code == ILCode.Ldloc && retArg.Operand == var2) { return true; } } @@ -118,9 +122,9 @@ namespace ICSharpCode.Decompiler.ILAst bool SkipDummyBr(ILBlock method, ref int i) { - ILExpression br; - if (i + 1 < method.Body.Count && method.Body[i].Match(ILCode.Br, out br)) { - if (br.Operand != method.Body[i + 1]) + ILLabel target; + if (i + 1 < method.Body.Count && method.Body[i].Match(ILCode.Br, out target)) { + if (target != method.Body[i + 1]) return false; i += 2; } @@ -154,13 +158,12 @@ namespace ICSharpCode.Decompiler.ILAst void AnalyzeCtor() { ILBlock method = CreateILAst(enumeratorCtor); + + ILExpression stfldPattern = new ILExpression(ILCode.Stfld, ILExpression.AnyOperand, LoadFromArgument.This, new LoadFromArgument(0)); + foreach (ILNode node in method.Body) { - ILExpression stfld; - if (node.Match(ILCode.Stfld, out stfld) - && stfld.Arguments[0].Code == ILCode.Ldarg && ((ParameterDefinition)stfld.Arguments[0].Operand).Index < 0 - && stfld.Arguments[1].Code == ILCode.Ldarg && ((ParameterDefinition)stfld.Arguments[1].Operand).Index == 0) - { - stateField = stfld.Operand as FieldDefinition; + if (stfldPattern.Match(node)) { + stateField = ((ILExpression)node).Operand as FieldDefinition; } } if (stateField == null) @@ -185,7 +188,7 @@ namespace ICSharpCode.Decompiler.ILAst #endregion #region Figure out what the 'current' field is - static readonly ILExpression returnFieldFromThisPattern = new ILExpression(ILCode.Ret, null, new ILExpression(ILCode.Ldfld, ILExpression.AnyOperand, LoadFromThis.Instance)); + static readonly ILExpression returnFieldFromThisPattern = new ILExpression(ILCode.Ret, null, new ILExpression(ILCode.Ldfld, ILExpression.AnyOperand, LoadFromArgument.This)); /// /// Looks at the enumerator's get_Current method and figures out which of the fields holds the current value. @@ -202,7 +205,7 @@ namespace ICSharpCode.Decompiler.ILAst currentField = ((ILExpression)method.Body[0]).Arguments[0].Operand as FieldDefinition; } } else { - StoreToVariable v = new StoreToVariable(new ILExpression(ILCode.Ldfld, ILExpression.AnyOperand, LoadFromThis.Instance)); + StoreToVariable v = new StoreToVariable(new ILExpression(ILCode.Ldfld, ILExpression.AnyOperand, LoadFromArgument.This)); if (v.Match(method.Body[0])) { int i = 1; if (SkipDummyBr(method, ref i) && i == method.Body.Count - 1) { @@ -228,7 +231,7 @@ namespace ICSharpCode.Decompiler.ILAst ILExpression mappingPattern = new ILExpression( ILCode.Stfld, ILExpression.AnyOperand, new AnyILExpression(), - new ILExpression(ILCode.Ldfld, ILExpression.AnyOperand, LoadFromThis.Instance)); + new ILExpression(ILCode.Ldfld, ILExpression.AnyOperand, LoadFromArgument.This)); ILBlock method = CreateILAst(getEnumeratorMethod); foreach (ILNode node in method.Body) { @@ -506,16 +509,16 @@ namespace ICSharpCode.Decompiler.ILAst if (ilMethod.Body.Count == 0) throw new YieldAnalysisFailedException(); - ILExpression lastReturn; - if (!ilMethod.Body.Last().Match(ILCode.Ret, out lastReturn)) + ILExpression lastReturnArg; + if (!ilMethod.Body.Last().Match(ILCode.Ret, out lastReturnArg)) throw new YieldAnalysisFailedException(); ilMethod.Body.RemoveAll(n => n.Match(ILCode.Nop)); // remove nops // There are two possibilities: - if (lastReturn.Arguments[0].Code == ILCode.Ldloc) { + if (lastReturnArg.Code == ILCode.Ldloc) { // a) the compiler uses a variable for returns (in debug builds, or when there are try-finally blocks) - returnVariable = (ILVariable)lastReturn.Arguments[0].Operand; + returnVariable = (ILVariable)lastReturnArg.Operand; returnLabel = ilMethod.Body.ElementAtOrDefault(ilMethod.Body.Count - 2) as ILLabel; if (returnLabel == null) throw new YieldAnalysisFailedException(); @@ -524,7 +527,7 @@ namespace ICSharpCode.Decompiler.ILAst returnVariable = null; returnLabel = null; // In this case, the last return must return false. - if (lastReturn.Arguments[0].Code != ILCode.Ldc_I4 || (int)lastReturn.Arguments[0].Operand != 0) + if (lastReturnArg.Code != ILCode.Ldc_I4 || (int)lastReturnArg.Operand != 0) throw new YieldAnalysisFailedException(); } @@ -574,7 +577,7 @@ namespace ICSharpCode.Decompiler.ILAst throw new YieldAnalysisFailedException(); // Verify that the first instruction is a switch on this.state, and that the 2nd instruction is br - if (!new ILExpression(ILCode.Switch, ILExpression.AnyOperand, new ILExpression(ILCode.Ldfld, stateField, LoadFromThis.Instance)).Match(body[0])) + if (!new ILExpression(ILCode.Switch, ILExpression.AnyOperand, new ILExpression(ILCode.Ldfld, stateField, LoadFromArgument.This)).Match(body[0])) throw new YieldAnalysisFailedException(); if (!body[1].Match(ILCode.Br)) @@ -597,8 +600,7 @@ namespace ICSharpCode.Decompiler.ILAst ILExpression call; if (!(faultBlock.Body.Count == 2 || faultBlock.Body.Count == 3)) throw new YieldAnalysisFailedException(); - if (!faultBlock.Body[0].Match(ILCode.Call, out call) && call.Operand == disposeMethod - && call.Arguments.Count == 1 && LoadFromThis.Instance.Match(call.Arguments[0])) + if (!new ILExpression(ILCode.Call, disposeMethod, LoadFromArgument.This).Match(faultBlock.Body[0])) throw new YieldAnalysisFailedException(); if (faultBlock.Body.Count == 3 && !faultBlock.Body[1].Match(ILCode.Nop)) throw new YieldAnalysisFailedException(); @@ -668,7 +670,7 @@ namespace ICSharpCode.Decompiler.ILAst // Copy all instructions from the old body to newBody. for (int pos = 2; pos < bodyLength; pos++) { ILExpression expr = body[pos] as ILExpression; - if (expr != null && expr.Code == ILCode.Stfld && LoadFromThis.Instance.Match(expr.Arguments[0])) { + if (expr != null && expr.Code == ILCode.Stfld && LoadFromArgument.This.Match(expr.Arguments[0])) { // Handle stores to 'state' or 'current' if (expr.Operand == stateField) { if (expr.Arguments[1].Code != ILCode.Ldc_I4) @@ -711,7 +713,7 @@ namespace ICSharpCode.Decompiler.ILAst } else { throw new YieldAnalysisFailedException(); } - } else if (expr != null && expr.Code == ILCode.Call && expr.Arguments.Count == 1 && LoadFromThis.Instance.Match(expr.Arguments[0])) { + } else if (expr != null && expr.Code == ILCode.Call && expr.Arguments.Count == 1 && LoadFromArgument.This.Match(expr.Arguments[0])) { MethodDefinition method = expr.Operand as MethodDefinition; if (method == null) throw new YieldAnalysisFailedException(); @@ -764,9 +766,10 @@ namespace ICSharpCode.Decompiler.ILAst ILBlock block = CreateILAst(finallyMethod); block.Body.RemoveAll(n => n.Match(ILCode.Nop)); // Get rid of assignment to state - ILExpression stfld; - if (block.Body.Count > 0 && block.Body[0].Match(ILCode.Stfld, out stfld)) { - if (stfld.Operand == stateField && LoadFromThis.Instance.Match(stfld.Arguments[0])) + FieldReference stfld; + List args; + if (block.Body.Count > 0 && block.Body[0].Match(ILCode.Stfld, out stfld, out args)) { + if (stfld == stateField && LoadFromArgument.This.Match(args[0])) block.Body.RemoveAt(0); } // Convert ret to endfinally @@ -788,7 +791,7 @@ namespace ICSharpCode.Decompiler.ILAst if (field != null) { switch (expr.Code) { case ILCode.Ldfld: - if (LoadFromThis.Instance.Match(expr.Arguments[0])) { + if (LoadFromArgument.This.Match(expr.Arguments[0])) { if (fieldToParameterMap.ContainsKey(field)) { expr.Code = ILCode.Ldarg; expr.Operand = fieldToParameterMap[field]; @@ -800,7 +803,7 @@ namespace ICSharpCode.Decompiler.ILAst } break; case ILCode.Stfld: - if (LoadFromThis.Instance.Match(expr.Arguments[0])) { + if (LoadFromArgument.This.Match(expr.Arguments[0])) { if (fieldToParameterMap.ContainsKey(field)) { expr.Code = ILCode.Starg; expr.Operand = fieldToParameterMap[field]; From ae0d6d52950f671b37027d903c597f32c135d194 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 6 Mar 2011 17:36:29 +0100 Subject: [PATCH 10/15] Some bugfixes for yield return. --- ICSharpCode.Decompiler/Ast/AstBuilder.cs | 3 +++ .../ILAst/YieldReturnDecompiler.cs | 17 +++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/ICSharpCode.Decompiler/Ast/AstBuilder.cs b/ICSharpCode.Decompiler/Ast/AstBuilder.cs index 96cfe6c33..7c49b3915 100644 --- a/ICSharpCode.Decompiler/Ast/AstBuilder.cs +++ b/ICSharpCode.Decompiler/Ast/AstBuilder.cs @@ -142,6 +142,8 @@ namespace ICSharpCode.Decompiler.Ast public TypeDeclaration CreateType(TypeDefinition typeDef) { + TypeDefinition oldCurrentType = context.CurrentType; + context.CurrentType = typeDef; TypeDeclaration astType = new TypeDeclaration(); astType.AddAnnotation(typeDef); astType.Modifiers = ConvertModifiers(typeDef); @@ -207,6 +209,7 @@ namespace ICSharpCode.Decompiler.Ast } ConvertAttributes(astType, typeDef); + context.CurrentType = oldCurrentType; return astType; } diff --git a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs index 87bb342a2..f12a68fbd 100644 --- a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs +++ b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs @@ -93,7 +93,7 @@ namespace ICSharpCode.Decompiler.ILAst break; if (ldloc.Code != ILCode.Ldloc || ldarg.Code != ILCode.Ldarg) return false; - if (ldloc.Operand != storedField || !(storedField is FieldDefinition)) + if (ldloc.Operand != var1 || !(storedField is FieldDefinition)) return false; fieldToParameterMap[(FieldDefinition)storedField] = (ParameterDefinition)ldarg.Operand; } @@ -377,11 +377,9 @@ namespace ICSharpCode.Decompiler.ILAst throw new YieldAnalysisFailedException(); MethodDefinition mdef = call.Operand as MethodDefinition; - if (mdef != null) { - if (finallyMethodToStateInterval.ContainsKey(mdef)) - throw new YieldAnalysisFailedException(); - finallyMethodToStateInterval.Add(mdef, interval); - } + if (mdef == null || finallyMethodToStateInterval.ContainsKey(mdef)) + throw new YieldAnalysisFailedException(); + finallyMethodToStateInterval.Add(mdef, interval); } ranges = null; } @@ -436,6 +434,13 @@ namespace ICSharpCode.Decompiler.ILAst break; case ILCode.Ret: break; + case ILCode.Call: + // in some cases (e.g. foreach over array) the C# compiler produces a finally method outside of try-finally blocks + MethodDefinition mdef = expr.Operand as MethodDefinition; + if (mdef == null || finallyMethodToStateInterval.ContainsKey(mdef)) + throw new YieldAnalysisFailedException(); + finallyMethodToStateInterval.Add(mdef, nodeRange.ToInterval()); + break; default: throw new YieldAnalysisFailedException(); } From bfcc0e779bd62263f18d4bd7babdd41338edf1e0 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 6 Mar 2011 19:29:14 +0100 Subject: [PATCH 11/15] YieldReturnDecompiler: Use symbolic execution to analyze the jump table at the start of MoveNext(). --- .../ILAst/YieldReturnDecompiler.cs | 386 ++++++++++-------- 1 file changed, 218 insertions(+), 168 deletions(-) diff --git a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs index f12a68fbd..2c361bda2 100644 --- a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs +++ b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs @@ -44,24 +44,33 @@ namespace ICSharpCode.Decompiler.ILAst if (!yrd.MatchEnumeratorCreationPattern(method)) return; yrd.enumeratorType = yrd.enumeratorCtor.DeclaringType; - #if !DEBUG - try { + #if DEBUG + if (Debugger.IsAttached) { + yrd.Run(); + } else { #endif - yrd.AnalyzeCtor(); - yrd.AnalyzeCurrentProperty(); - yrd.ResolveIEnumerableIEnumeratorFieldMapping(); - yrd.ConstructExceptionTable(); - yrd.AnalyzeMoveNext(); - yrd.TranslateFieldsToLocalAccess(); - #if !DEBUG - } catch (YieldAnalysisFailedException) { - return; + try { + yrd.Run(); + } catch (YieldAnalysisFailedException) { + return; + } + #if DEBUG } #endif method.Body.Clear(); method.EntryGoto = null; method.Body.AddRange(yrd.newBody); } + + void Run() + { + AnalyzeCtor(); + AnalyzeCurrentProperty(); + ResolveIEnumerableIEnumeratorFieldMapping(); + ConstructExceptionTable(); + AnalyzeMoveNext(); + TranslateFieldsToLocalAccess(); + } #endregion #region Match the enumerator creation pattern @@ -139,11 +148,16 @@ namespace ICSharpCode.Decompiler.ILAst return false; if (expr.Arguments[0].Code != ILCode.Ldc_I4 || (int)expr.Arguments[0].Operand != -2) return false; - if (ctor == null || !ctor.DeclaringType.IsCompilerGenerated()) + if (ctor == null || ctor.DeclaringType.DeclaringType != context.CurrentType) return false; - if (ctor.DeclaringType.DeclaringType != context.CurrentType) + return IsCompilerGeneratorEnumerator(ctor.DeclaringType); + } + + public static bool IsCompilerGeneratorEnumerator(TypeDefinition type) + { + if (!(type.Name.StartsWith("<", StringComparison.Ordinal) && type.IsCompilerGenerated())) return false; - foreach (TypeReference i in ctor.DeclaringType.Interfaces) { + foreach (TypeReference i in type.Interfaces) { if (i.Namespace == "System.Collections" && i.Name == "IEnumerator") return true; } @@ -151,7 +165,7 @@ namespace ICSharpCode.Decompiler.ILAst } #endregion - #region Figure out what the 'state' field is + #region Figure out what the 'state' field is (analysis of .ctor()) /// /// Looks at the enumerator's ctor and figures out which of the fields holds the state. /// @@ -187,7 +201,7 @@ namespace ICSharpCode.Decompiler.ILAst } #endregion - #region Figure out what the 'current' field is + #region Figure out what the 'current' field is (analysis of get_Current()) static readonly ILExpression returnFieldFromThisPattern = new ILExpression(ILCode.Ret, null, new ILExpression(ILCode.Ldfld, ILExpression.AnyOperand, LoadFromArgument.This)); /// @@ -220,7 +234,7 @@ namespace ICSharpCode.Decompiler.ILAst } #endregion - #region Figure out the mapping of IEnumerable fields to IEnumerator fields + #region Figure out the mapping of IEnumerable fields to IEnumerator fields (analysis of GetEnumerator()) void ResolveIEnumerableIEnumeratorFieldMapping() { MethodDefinition getEnumeratorMethod = enumeratorType.Methods.FirstOrDefault( @@ -249,7 +263,7 @@ namespace ICSharpCode.Decompiler.ILAst } #endregion - #region Construction of the exception table + #region Construction of the exception table (analysis of Dispose()) // We construct the exception table by analyzing the enumerator's Dispose() method. // Assumption: there are no loops/backward jumps @@ -259,6 +273,44 @@ namespace ICSharpCode.Decompiler.ILAst // This is (int.MinValue, int.MaxValue) for the first instruction. // These ranges are propagated depending on the conditional jumps performed by the code. + Dictionary finallyMethodToStateInterval; + + void ConstructExceptionTable() + { + disposeMethod = enumeratorType.Methods.FirstOrDefault(m => m.Name == "System.IDisposable.Dispose"); + ILBlock ilMethod = CreateILAst(disposeMethod); + + finallyMethodToStateInterval = new Dictionary(); + + InitStateRanges(ilMethod.Body[0]); + AssignStateRanges(ilMethod.Body, ilMethod.Body.Count, forDispose: true); + + // Now look at the finally blocks: + foreach (var tryFinally in ilMethod.GetSelfAndChildrenRecursive()) { + Interval interval = ranges[tryFinally.TryBlock.Body[0]].ToEnclosingInterval(); + var finallyBody = tryFinally.FinallyBlock.Body; + if (!(finallyBody.Count == 2 || finallyBody.Count == 3)) + throw new YieldAnalysisFailedException(); + ILExpression call = finallyBody[0] as ILExpression; + if (call == null || call.Code != ILCode.Call || call.Arguments.Count != 1) + throw new YieldAnalysisFailedException(); + if (call.Arguments[0].Code != ILCode.Ldarg || ((ParameterDefinition)call.Arguments[0].Operand).Index >= 0) + throw new YieldAnalysisFailedException(); + if (finallyBody.Count == 3 && !finallyBody[1].Match(ILCode.Nop)) + throw new YieldAnalysisFailedException(); + if (!finallyBody[finallyBody.Count - 1].Match(ILCode.Endfinally)) + throw new YieldAnalysisFailedException(); + + MethodDefinition mdef = call.Operand as MethodDefinition; + if (mdef == null || finallyMethodToStateInterval.ContainsKey(mdef)) + throw new YieldAnalysisFailedException(); + finallyMethodToStateInterval.Add(mdef, interval); + } + ranges = null; + } + #endregion + + #region Assign StateRanges / Symbolic Execution (used for analysis of Dispose() and MoveNext()) #region struct Interval / class StateRange struct Interval { @@ -290,6 +342,15 @@ namespace ICSharpCode.Decompiler.ILAst this.data.Add(new Interval(start, end)); } + public bool Contains(int val) + { + foreach (Interval v in data) { + if (v.Start <= val && val <= v.End) + return true; + } + return false; + } + public void UnionWith(StateRange other) { data.AddRange(other.data); @@ -338,120 +399,139 @@ namespace ICSharpCode.Decompiler.ILAst return string.Join(",", data); } - public Interval ToInterval() + public Interval ToEnclosingInterval() { - if (data.Count == 1) - return data[0]; - else + if (data.Count == 0) throw new YieldAnalysisFailedException(); + return new Interval(data[0].Start, data[data.Count - 1].End); } } #endregion DefaultDictionary ranges; - Dictionary finallyMethodToStateInterval = new Dictionary(); + ILVariable rangeAnalysisStateVariable; - void ConstructExceptionTable() + /// + /// Initializes the state range logic: + /// Clears 'ranges' and sets 'ranges[entryPoint]' to the full range (int.MinValue to int.MaxValue) + /// + void InitStateRanges(ILNode entryPoint) { - disposeMethod = enumeratorType.Methods.FirstOrDefault(m => m.Name == "System.IDisposable.Dispose"); - ILBlock ilMethod = CreateILAst(disposeMethod); - - ranges = new DefaultDictionary(node => new StateRange()); - ranges[ilMethod] = new StateRange(int.MinValue, int.MaxValue); - AssignStateRanges(ilMethod); - - // Now look at the finally blocks: - foreach (var tryFinally in ilMethod.GetSelfAndChildrenRecursive()) { - Interval interval = ranges[tryFinally.TryBlock.Body[0]].ToInterval(); - var finallyBody = tryFinally.FinallyBlock.Body; - if (!(finallyBody.Count == 2 || finallyBody.Count == 3)) - throw new YieldAnalysisFailedException(); - ILExpression call = finallyBody[0] as ILExpression; - if (call == null || call.Code != ILCode.Call || call.Arguments.Count != 1) - throw new YieldAnalysisFailedException(); - if (call.Arguments[0].Code != ILCode.Ldarg || ((ParameterDefinition)call.Arguments[0].Operand).Index >= 0) - throw new YieldAnalysisFailedException(); - if (finallyBody.Count == 3 && !finallyBody[1].Match(ILCode.Nop)) - throw new YieldAnalysisFailedException(); - if (!finallyBody[finallyBody.Count - 1].Match(ILCode.Endfinally)) - throw new YieldAnalysisFailedException(); - - MethodDefinition mdef = call.Operand as MethodDefinition; - if (mdef == null || finallyMethodToStateInterval.ContainsKey(mdef)) - throw new YieldAnalysisFailedException(); - finallyMethodToStateInterval.Add(mdef, interval); - } - ranges = null; + ranges = new DefaultDictionary(n => new StateRange()); + ranges[entryPoint] = new StateRange(int.MinValue, int.MaxValue); + rangeAnalysisStateVariable = null; } - - #region Assign StateRanges / Symbolic Execution - void AssignStateRanges(ILBlock block) + + int AssignStateRanges(List body, int bodyLength, bool forDispose) { - if (block.Body.Count == 0) - return; - ranges[block.Body[0]].UnionWith(ranges[block]); - for (int i = 0; i < block.Body.Count; i++) { - StateRange nodeRange = ranges[block.Body[i]]; + if (bodyLength == 0) + return 0; + for (int i = 0; i < bodyLength; i++) { + StateRange nodeRange = ranges[body[i]]; nodeRange.Simplify(); - ILLabel label = block.Body[i] as ILLabel; + ILLabel label = body[i] as ILLabel; if (label != null) { - ranges[block.Body[i + 1]].UnionWith(nodeRange); + ranges[body[i + 1]].UnionWith(nodeRange); continue; } - ILTryCatchBlock tryFinally = block.Body[i] as ILTryCatchBlock; + ILTryCatchBlock tryFinally = body[i] as ILTryCatchBlock; if (tryFinally != null) { - if (tryFinally.CatchBlocks.Count != 0 || tryFinally.FaultBlock != null || tryFinally.FinallyBlock == null) + if (!forDispose || tryFinally.CatchBlocks.Count != 0 || tryFinally.FaultBlock != null || tryFinally.FinallyBlock == null) throw new YieldAnalysisFailedException(); ranges[tryFinally.TryBlock].UnionWith(nodeRange); - AssignStateRanges(tryFinally.TryBlock); + AssignStateRanges(tryFinally.TryBlock.Body, tryFinally.TryBlock.Body.Count, forDispose); continue; } - ILExpression expr = block.Body[i] as ILExpression; + ILExpression expr = body[i] as ILExpression; if (expr == null) throw new YieldAnalysisFailedException(); switch (expr.Code) { case ILCode.Switch: - SymbolicValue val = Eval(expr.Arguments[0]); - if (val.Type != SymbolicValueType.State) - throw new YieldAnalysisFailedException(); - ILLabel[] targetLabels = (ILLabel[])expr.Operand; - for (int j = 0; j < targetLabels.Length; j++) { - int state = j - val.Constant; - ranges[targetLabels[j]].UnionWith(nodeRange, state, state); + { + SymbolicValue val = Eval(expr.Arguments[0]); + if (val.Type != SymbolicValueType.State) + throw new YieldAnalysisFailedException(); + ILLabel[] targetLabels = (ILLabel[])expr.Operand; + for (int j = 0; j < targetLabels.Length; j++) { + int state = j - val.Constant; + ranges[targetLabels[j]].UnionWith(nodeRange, state, state); + } + StateRange nextRange = ranges[body[i + 1]]; + nextRange.UnionWith(nodeRange, int.MinValue, -1 - val.Constant); + nextRange.UnionWith(nodeRange, targetLabels.Length - val.Constant, int.MaxValue); + break; } - ranges[block.Body[i + 1]].UnionWith(nodeRange, int.MinValue, -1 - val.Constant); - ranges[block.Body[i + 1]].UnionWith(nodeRange, targetLabels.Length - val.Constant, int.MaxValue); - break; case ILCode.Br: case ILCode.Leave: ranges[(ILLabel)expr.Operand].UnionWith(nodeRange); break; + case ILCode.Brtrue: + { + SymbolicValue val = Eval(expr.Arguments[0]); + if (val.Type != SymbolicValueType.StateEquals) + throw new YieldAnalysisFailedException(); + ranges[(ILLabel)expr.Operand].UnionWith(nodeRange, val.Constant, val.Constant); + StateRange nextRange = ranges[body[i + 1]]; + nextRange.UnionWith(nodeRange, int.MinValue, val.Constant - 1); + nextRange.UnionWith(nodeRange, val.Constant + 1, int.MaxValue); + break; + } case ILCode.Nop: - ranges[block.Body[i + 1]].UnionWith(nodeRange); + ranges[body[i + 1]].UnionWith(nodeRange); break; case ILCode.Ret: break; + case ILCode.Stloc: + { + SymbolicValue val = Eval(expr.Arguments[0]); + if (val.Type == SymbolicValueType.State && val.Constant == 0 && rangeAnalysisStateVariable == null) + rangeAnalysisStateVariable = (ILVariable)expr.Operand; + else + throw new YieldAnalysisFailedException(); + goto case ILCode.Nop; + } case ILCode.Call: // in some cases (e.g. foreach over array) the C# compiler produces a finally method outside of try-finally blocks - MethodDefinition mdef = expr.Operand as MethodDefinition; - if (mdef == null || finallyMethodToStateInterval.ContainsKey(mdef)) + if (forDispose) { + MethodDefinition mdef = expr.Operand as MethodDefinition; + if (mdef == null || finallyMethodToStateInterval.ContainsKey(mdef)) + throw new YieldAnalysisFailedException(); + finallyMethodToStateInterval.Add(mdef, nodeRange.ToEnclosingInterval()); + } else { throw new YieldAnalysisFailedException(); - finallyMethodToStateInterval.Add(mdef, nodeRange.ToInterval()); + } break; default: - throw new YieldAnalysisFailedException(); + if (forDispose) + throw new YieldAnalysisFailedException(); + else + return i; } } + return bodyLength; } enum SymbolicValueType { + /// + /// int: Constant (result of ldc.i4) + /// IntegerConstant, + /// + /// int: State + Constant + /// State, - This + /// + /// This pointer (result of ldarg.0) + /// + This, + /// + /// bool: State == Constant + /// + StateEquals } struct SymbolicValue @@ -473,11 +553,12 @@ namespace ICSharpCode.Decompiler.ILAst SymbolicValue Eval(ILExpression expr) { + SymbolicValue left, right; switch (expr.Code) { case ILCode.Sub: - SymbolicValue left = Eval(expr.Arguments[0]); - SymbolicValue right = Eval(expr.Arguments[1]); - if (left.Type != SymbolicValueType.State && right.Type != SymbolicValueType.IntegerConstant) + left = Eval(expr.Arguments[0]); + right = Eval(expr.Arguments[1]); + if (left.Type != SymbolicValueType.State && left.Type != SymbolicValueType.IntegerConstant) throw new YieldAnalysisFailedException(); if (right.Type != SymbolicValueType.IntegerConstant) throw new YieldAnalysisFailedException(); @@ -488,6 +569,11 @@ namespace ICSharpCode.Decompiler.ILAst if (expr.Operand != stateField) throw new YieldAnalysisFailedException(); return new SymbolicValue(SymbolicValueType.State); + case ILCode.Ldloc: + if (expr.Operand == rangeAnalysisStateVariable) + return new SymbolicValue(SymbolicValueType.State); + else + throw new YieldAnalysisFailedException(); case ILCode.Ldarg: if (((ParameterDefinition)expr.Operand).Index < 0) return new SymbolicValue(SymbolicValueType.This); @@ -495,14 +581,21 @@ namespace ICSharpCode.Decompiler.ILAst throw new YieldAnalysisFailedException(); case ILCode.Ldc_I4: return new SymbolicValue(SymbolicValueType.IntegerConstant, (int)expr.Operand); + case ILCode.Ceq: + left = Eval(expr.Arguments[0]); + right = Eval(expr.Arguments[1]); + if (left.Type != SymbolicValueType.State || right.Type != SymbolicValueType.IntegerConstant) + throw new YieldAnalysisFailedException(); + // bool: (state + left.Constant == right.Constant) + // bool: (state == right.Constant - left.Constant) + return new SymbolicValue(SymbolicValueType.StateEquals, unchecked ( right.Constant - left.Constant )); default: throw new YieldAnalysisFailedException(); } } #endregion - #endregion - #region Analysis and Transformation of MoveNext() + #region Analysis of MoveNext() ILVariable returnVariable; ILLabel returnLabel; ILLabel returnFalseLabel; @@ -547,7 +640,16 @@ namespace ICSharpCode.Decompiler.ILAst if (tryFaultBlock.CatchBlocks.Count != 0 || tryFaultBlock.FinallyBlock != null || tryFaultBlock.FaultBlock == null) throw new YieldAnalysisFailedException(); - VerifyFaultBlock(tryFaultBlock.FaultBlock); + ILBlock faultBlock = tryFaultBlock.FaultBlock; + // Ensure the fault block contains the call to Dispose(). + if (!(faultBlock.Body.Count == 2 || faultBlock.Body.Count == 3)) + throw new YieldAnalysisFailedException(); + if (!new ILExpression(ILCode.Call, disposeMethod, LoadFromArgument.This).Match(faultBlock.Body[0])) + throw new YieldAnalysisFailedException(); + if (faultBlock.Body.Count == 3 && !faultBlock.Body[1].Match(ILCode.Nop)) + throw new YieldAnalysisFailedException(); + if (!faultBlock.Body[faultBlock.Body.Count - 1].Match(ILCode.Endfinally)) + throw new YieldAnalysisFailedException(); body = tryFaultBlock.TryBlock.Body; body.RemoveAll(n => n.Match(ILCode.Nop)); // remove nops @@ -578,81 +680,27 @@ namespace ICSharpCode.Decompiler.ILAst bodyLength -= 2; // don't conside the 'ret(false)' part of the body } returnFalseLabel = body.ElementAtOrDefault(--bodyLength) as ILLabel; - if (returnFalseLabel == null || bodyLength < 2) + if (returnFalseLabel == null || bodyLength == 0) throw new YieldAnalysisFailedException(); - // Verify that the first instruction is a switch on this.state, and that the 2nd instruction is br - if (!new ILExpression(ILCode.Switch, ILExpression.AnyOperand, new ILExpression(ILCode.Ldfld, stateField, LoadFromArgument.This)).Match(body[0])) - throw new YieldAnalysisFailedException(); - - if (!body[1].Match(ILCode.Br)) - throw new YieldAnalysisFailedException(); + InitStateRanges(body[0]); + int pos = AssignStateRanges(body, bodyLength, forDispose: false); + while (pos > 0 && body[pos - 1] is ILLabel) + pos--; - SimplifySwitch(body, ref bodyLength); - - // verify that the br (if no state is matched) leads to 'return false' - if (((ILExpression)body[1]).Operand != returnFalseLabel) - throw new YieldAnalysisFailedException(); - - ConvertBody(body, bodyLength); - } - - /// - /// Ensure the fault block contains the call to Dispose(). - /// - void VerifyFaultBlock(ILBlock faultBlock) - { - ILExpression call; - if (!(faultBlock.Body.Count == 2 || faultBlock.Body.Count == 3)) - throw new YieldAnalysisFailedException(); - if (!new ILExpression(ILCode.Call, disposeMethod, LoadFromArgument.This).Match(faultBlock.Body[0])) - throw new YieldAnalysisFailedException(); - if (faultBlock.Body.Count == 3 && !faultBlock.Body[1].Match(ILCode.Nop)) - throw new YieldAnalysisFailedException(); - if (!faultBlock.Body[faultBlock.Body.Count - 1].Match(ILCode.Endfinally)) - throw new YieldAnalysisFailedException(); - } - - /// - /// Simplifies the switch statement at body[0], and the branch at body[1]. - /// - void SimplifySwitch(List body, ref int bodyLength) - { - HashSet regularLabels = new HashSet(); - Dictionary simplications = new Dictionary(); - for (int i = 2; i < bodyLength; i++) { - ILExpression expr = body[i] as ILExpression; - if (expr != null && expr.Operand is ILLabel) - regularLabels.Add((ILLabel)expr.Operand); - } - for (int i = 2; i + 1 < bodyLength; i += 2) { + List> labels = new List>(); + for (int i = pos; i < bodyLength; i++) { ILLabel label = body[i] as ILLabel; - if (label == null || regularLabels.Contains(label)) - break; - ILExpression expr = body[i + 1] as ILExpression; - if (expr != null && expr.Code == ILCode.Br) { - simplications.Add(label, (ILLabel)expr.Operand); - } else { - break; + if (label != null) { + labels.Add(new KeyValuePair(label, ranges[label])); } } - ILExpression switchExpr = (ILExpression)body[0]; - ILExpression brExpr = (ILExpression)body[1]; - Debug.Assert(switchExpr.Code == ILCode.Switch); - Debug.Assert(brExpr.Code == ILCode.Br); - ILLabel targetLabel; - if (simplications.TryGetValue((ILLabel)brExpr.Operand, out targetLabel)) - brExpr.Operand = targetLabel; - ILLabel[] labels = (ILLabel[])switchExpr.Operand; - for (int i = 0; i < labels.Length; i++) { - if (simplications.TryGetValue(labels[i], out targetLabel)) - labels[i] = targetLabel; - } - // remove the labels that aren't used anymore - body.RemoveRange(2, simplications.Count * 2); - bodyLength -= simplications.Count * 2; + + ConvertBody(body, pos, bodyLength, labels); } + #endregion + #region ConvertBody struct SetState { public readonly int NewBodyPos; @@ -665,15 +713,14 @@ namespace ICSharpCode.Decompiler.ILAst } } - void ConvertBody(List body, int bodyLength) + void ConvertBody(List body, int startPos, int bodyLength, List> labels) { newBody = new List(); - ILLabel[] switchLabels = (ILLabel[])((ILExpression)body[0]).Operand; - newBody.Add(MakeGoTo(switchLabels[0])); + newBody.Add(MakeGoTo(labels, 0)); List stateChanges = new List(); int currentState = -1; // Copy all instructions from the old body to newBody. - for (int pos = 2; pos < bodyLength; pos++) { + for (int pos = startPos; pos < bodyLength; pos++) { ILExpression expr = body[pos] as ILExpression; if (expr != null && expr.Code == ILCode.Stfld && LoadFromArgument.This.Match(expr.Arguments[0])) { // Handle stores to 'state' or 'current' @@ -696,10 +743,7 @@ namespace ICSharpCode.Decompiler.ILAst if (val == 0) { newBody.Add(MakeGoTo(returnFalseLabel)); } else if (val == 1) { - if (currentState >= 0 && currentState < switchLabels.Length) - newBody.Add(MakeGoTo(switchLabels[currentState])); - else - newBody.Add(MakeGoTo(returnFalseLabel)); + newBody.Add(MakeGoTo(labels, currentState)); } else { throw new YieldAnalysisFailedException(); } @@ -711,10 +755,7 @@ namespace ICSharpCode.Decompiler.ILAst if (val == 0) { newBody.Add(MakeGoTo(returnFalseLabel)); } else if (val == 1) { - if (currentState >= 0 && currentState < switchLabels.Length) - newBody.Add(MakeGoTo(switchLabels[currentState])); - else - newBody.Add(MakeGoTo(returnFalseLabel)); + newBody.Add(MakeGoTo(labels, currentState)); } else { throw new YieldAnalysisFailedException(); } @@ -766,6 +807,15 @@ namespace ICSharpCode.Decompiler.ILAst return new ILExpression(ILCode.Br, targetLabel); } + ILExpression MakeGoTo(List> labels, int state) + { + foreach (var pair in labels) { + if (pair.Value.Contains(state)) + return MakeGoTo(pair.Key); + } + throw new YieldAnalysisFailedException(); + } + ILBlock ConvertFinallyBlock(MethodDefinition finallyMethod) { ILBlock block = CreateILAst(finallyMethod); From 69cad52cb4a7e8da07886ac0d1c8c63757431722 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 6 Mar 2011 19:29:36 +0100 Subject: [PATCH 12/15] Hide compiler-generated enumerator classes when "yield return" decompilation is enabled. --- ICSharpCode.Decompiler/Ast/AstBuilder.cs | 32 +++++++++++++++--------- ILSpy/CSharpLanguage.cs | 2 +- ILSpy/DecompilationOptions.cs | 2 +- ILSpy/DecompilerSettingsPanel.xaml.cs | 10 ++++++++ ILSpy/MainWindow.xaml.cs | 11 +++++--- ILSpy/OptionsDialog.xaml.cs | 4 ++- 6 files changed, 43 insertions(+), 18 deletions(-) diff --git a/ICSharpCode.Decompiler/Ast/AstBuilder.cs b/ICSharpCode.Decompiler/Ast/AstBuilder.cs index 7c49b3915..0c249b6c6 100644 --- a/ICSharpCode.Decompiler/Ast/AstBuilder.cs +++ b/ICSharpCode.Decompiler/Ast/AstBuilder.cs @@ -33,19 +33,27 @@ namespace ICSharpCode.Decompiler.Ast this.context = context; } - public static bool MemberIsHidden(MemberReference member) + public static bool MemberIsHidden(MemberReference member, DecompilerSettings settings) { MethodDefinition method = member as MethodDefinition; - if (method != null && (method.IsGetter || method.IsSetter || method.IsAddOn || method.IsRemoveOn)) - return true; - if (method != null && method.Name.StartsWith("<", StringComparison.Ordinal) && method.IsCompilerGenerated()) - return true; + if (method != null) { + if (method.IsGetter || method.IsSetter || method.IsAddOn || method.IsRemoveOn) + return true; + if (settings.AnonymousMethods && method.Name.StartsWith("<", StringComparison.Ordinal) && method.IsCompilerGenerated()) + return true; + } TypeDefinition type = member as TypeDefinition; - if (type != null && type.DeclaringType != null && type.Name.StartsWith("<>c__DisplayClass", StringComparison.Ordinal) && type.IsCompilerGenerated()) - return true; + if (type != null && type.DeclaringType != null) { + if (settings.AnonymousMethods && type.Name.StartsWith("<>c__DisplayClass", StringComparison.Ordinal) && type.IsCompilerGenerated()) + return true; + if (settings.YieldReturn && YieldReturnDecompiler.IsCompilerGeneratorEnumerator(type)) + return true; + } FieldDefinition field = member as FieldDefinition; - if (field != null && field.Name.StartsWith("CS$<>", StringComparison.Ordinal) && field.IsCompilerGenerated()) - return true; + if (field != null) { + if (settings.AnonymousMethods && field.Name.StartsWith("CS$<>", StringComparison.Ordinal) && field.IsCompilerGenerated()) + return true; + } return false; } @@ -170,7 +178,7 @@ namespace ICSharpCode.Decompiler.Ast // Nested types foreach(TypeDefinition nestedTypeDef in typeDef.NestedTypes) { - if (MemberIsHidden(nestedTypeDef)) + if (MemberIsHidden(nestedTypeDef, context.Settings)) continue; astType.AddChild(CreateType(nestedTypeDef), TypeDeclaration.MemberRole); } @@ -487,7 +495,7 @@ namespace ICSharpCode.Decompiler.Ast { // Add fields foreach(FieldDefinition fieldDef in typeDef.Fields) { - if (MemberIsHidden(fieldDef)) continue; + if (MemberIsHidden(fieldDef, context.Settings)) continue; astType.AddChild(CreateField(fieldDef), TypeDeclaration.MemberRole); } @@ -510,7 +518,7 @@ namespace ICSharpCode.Decompiler.Ast // Add methods foreach(MethodDefinition methodDef in typeDef.Methods) { - if (methodDef.IsConstructor || MemberIsHidden(methodDef)) continue; + if (methodDef.IsConstructor || MemberIsHidden(methodDef, context.Settings)) continue; astType.AddChild(CreateMethod(methodDef), TypeDeclaration.MemberRole); } diff --git a/ILSpy/CSharpLanguage.cs b/ILSpy/CSharpLanguage.cs index 60fc12388..5c24eadb6 100644 --- a/ILSpy/CSharpLanguage.cs +++ b/ILSpy/CSharpLanguage.cs @@ -420,7 +420,7 @@ namespace ICSharpCode.ILSpy public override bool ShowMember(MemberReference member) { - return showAllMembers || !AstBuilder.MemberIsHidden(member); + return showAllMembers || !AstBuilder.MemberIsHidden(member, new DecompilationOptions().DecompilerSettings); } } } diff --git a/ILSpy/DecompilationOptions.cs b/ILSpy/DecompilationOptions.cs index bcd59af6b..162fbe29c 100644 --- a/ILSpy/DecompilationOptions.cs +++ b/ILSpy/DecompilationOptions.cs @@ -54,7 +54,7 @@ namespace ICSharpCode.ILSpy public DecompilationOptions() { - this.DecompilerSettings = DecompilerSettingsPanel.LoadDecompilerSettings(ILSpySettings.Load()); + this.DecompilerSettings = DecompilerSettingsPanel.CurrentDecompilerSettings; } } } diff --git a/ILSpy/DecompilerSettingsPanel.xaml.cs b/ILSpy/DecompilerSettingsPanel.xaml.cs index 694ad1040..03d45eef8 100644 --- a/ILSpy/DecompilerSettingsPanel.xaml.cs +++ b/ILSpy/DecompilerSettingsPanel.xaml.cs @@ -31,6 +31,14 @@ namespace ICSharpCode.ILSpy this.DataContext = LoadDecompilerSettings(settings); } + static DecompilerSettings currentDecompilerSettings; + + public static DecompilerSettings CurrentDecompilerSettings { + get { + return currentDecompilerSettings ?? (currentDecompilerSettings = LoadDecompilerSettings(ILSpySettings.Load())); + } + } + public static DecompilerSettings LoadDecompilerSettings(ILSpySettings settings) { XElement e = settings["DecompilerSettings"]; @@ -52,6 +60,8 @@ namespace ICSharpCode.ILSpy existingElement.ReplaceWith(section); else root.Add(section); + + currentDecompilerSettings = null; // invalidate cached settings } } } \ No newline at end of file diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index 90867ade9..88b28ff1a 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -281,15 +281,20 @@ namespace ICSharpCode.ILSpy } void filterSettings_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + RefreshTreeViewFilter(); + if (e.PropertyName == "Language") { + TreeView_SelectionChanged(null, null); + } + } + + public void RefreshTreeViewFilter() { // filterSettings is mutable; but the ILSpyTreeNode filtering assumes that filter settings are immutable. // Thus, the main window will use one mutable instance (for data-binding), and assign a new clone to the ILSpyTreeNodes whenever the main // mutable instance changes. if (assemblyListTreeNode != null) assemblyListTreeNode.FilterSettings = sessionSettings.FilterSettings.Clone(); - if (e.PropertyName == "Language") { - TreeView_SelectionChanged(null, null); - } } internal AssemblyList AssemblyList { diff --git a/ILSpy/OptionsDialog.xaml.cs b/ILSpy/OptionsDialog.xaml.cs index dc87b88bf..fa975c82f 100644 --- a/ILSpy/OptionsDialog.xaml.cs +++ b/ILSpy/OptionsDialog.xaml.cs @@ -86,8 +86,10 @@ namespace ICSharpCode.ILSpy { OptionsDialog dlg = new OptionsDialog(); dlg.Owner = MainWindow.Instance; - if (dlg.ShowDialog() == true) + if (dlg.ShowDialog() == true) { + MainWindow.Instance.RefreshTreeViewFilter(); MainWindow.Instance.RefreshDecompiledView(); + } } } } \ No newline at end of file From 69fe6bc7c26a91fe75562a143a86e434ebb4690b Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 6 Mar 2011 19:42:44 +0100 Subject: [PATCH 13/15] Fixed bug in yield return decompiler when decompiling a method consisting only of 'yield break;'. --- .../ILAst/YieldReturnDecompiler.cs | 48 +++++++++++++++---- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs index 2c361bda2..4414970c5 100644 --- a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs +++ b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs @@ -471,12 +471,19 @@ namespace ICSharpCode.Decompiler.ILAst case ILCode.Brtrue: { SymbolicValue val = Eval(expr.Arguments[0]); - if (val.Type != SymbolicValueType.StateEquals) + if (val.Type == SymbolicValueType.StateEquals) { + ranges[(ILLabel)expr.Operand].UnionWith(nodeRange, val.Constant, val.Constant); + StateRange nextRange = ranges[body[i + 1]]; + nextRange.UnionWith(nodeRange, int.MinValue, val.Constant - 1); + nextRange.UnionWith(nodeRange, val.Constant + 1, int.MaxValue); + } else if (val.Type == SymbolicValueType.StateInEquals) { + ranges[body[i + 1]].UnionWith(nodeRange, val.Constant, val.Constant); + StateRange targetRange = ranges[(ILLabel)expr.Operand]; + targetRange.UnionWith(nodeRange, int.MinValue, val.Constant - 1); + targetRange.UnionWith(nodeRange, val.Constant + 1, int.MaxValue); + } else { throw new YieldAnalysisFailedException(); - ranges[(ILLabel)expr.Operand].UnionWith(nodeRange, val.Constant, val.Constant); - StateRange nextRange = ranges[body[i + 1]]; - nextRange.UnionWith(nodeRange, int.MinValue, val.Constant - 1); - nextRange.UnionWith(nodeRange, val.Constant + 1, int.MaxValue); + } break; } case ILCode.Nop: @@ -531,7 +538,11 @@ namespace ICSharpCode.Decompiler.ILAst /// /// bool: State == Constant /// - StateEquals + StateEquals, + /// + /// bool: State != Constant + /// + StateInEquals } struct SymbolicValue @@ -589,6 +600,14 @@ namespace ICSharpCode.Decompiler.ILAst // bool: (state + left.Constant == right.Constant) // bool: (state == right.Constant - left.Constant) return new SymbolicValue(SymbolicValueType.StateEquals, unchecked ( right.Constant - left.Constant )); + case ILCode.LogicNot: + SymbolicValue val = Eval(expr.Arguments[0]); + if (val.Type == SymbolicValueType.StateEquals) + return new SymbolicValue(SymbolicValueType.StateInEquals, val.Constant); + else if (val.Type == SymbolicValueType.StateInEquals) + return new SymbolicValue(SymbolicValueType.StateEquals, val.Constant); + else + throw new YieldAnalysisFailedException(); default: throw new YieldAnalysisFailedException(); } @@ -679,14 +698,24 @@ namespace ICSharpCode.Decompiler.ILAst bodyLength -= 2; // don't conside the 'ret(false)' part of the body } - returnFalseLabel = body.ElementAtOrDefault(--bodyLength) as ILLabel; - if (returnFalseLabel == null || bodyLength == 0) + // verify that the last element in the body is a label pointing to the 'ret(false)' + returnFalseLabel = body.ElementAtOrDefault(bodyLength - 1) as ILLabel; + if (returnFalseLabel == null) throw new YieldAnalysisFailedException(); InitStateRanges(body[0]); int pos = AssignStateRanges(body, bodyLength, forDispose: false); - while (pos > 0 && body[pos - 1] is ILLabel) + if (pos > 0 && body[pos - 1] is ILLabel) { pos--; + } else { + // ensure that the first element at body[pos] is a label: + ILLabel newLabel = new ILLabel(); + newLabel.Name = "YieldReturnEntryPoint"; + ranges[newLabel] = ranges[body[pos]]; // give the label the range of the instruction at body[pos] + + body.Insert(pos, newLabel); + bodyLength++; + } List> labels = new List>(); for (int i = pos; i < bodyLength; i++) { @@ -795,7 +824,6 @@ namespace ICSharpCode.Decompiler.ILAst newBody.Add(body[pos]); } } - newBody.Add(returnFalseLabel); newBody.Add(new ILExpression(ILCode.YieldBreak, null)); } From 28a98e5ccb44f3fdb2472bca2375643f4fa763e2 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 6 Mar 2011 19:46:23 +0100 Subject: [PATCH 14/15] Fix detection of "yield return" when IEnumerator is returned (not IEnumerable). --- ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs index 4414970c5..35e711738 100644 --- a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs +++ b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs @@ -146,7 +146,10 @@ namespace ICSharpCode.Decompiler.ILAst ctor = expr.Operand as MethodDefinition; if (expr.Code != ILCode.Newobj || expr.Arguments.Count != 1) return false; - if (expr.Arguments[0].Code != ILCode.Ldc_I4 || (int)expr.Arguments[0].Operand != -2) + if (expr.Arguments[0].Code != ILCode.Ldc_I4) + return false; + int initialState = (int)expr.Arguments[0].Operand; + if (!(initialState == -2 || initialState == 0)) return false; if (ctor == null || ctor.DeclaringType.DeclaringType != context.CurrentType) return false; From cdc56cb98ae1637f10a7286349d4affb570662d4 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 6 Mar 2011 20:11:44 +0100 Subject: [PATCH 15/15] Fix "yield return" decompilation in generic classes. --- .../ILAst/YieldReturnDecompiler.cs | 54 +++++++++++++------ 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs index 35e711738..28da4308e 100644 --- a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs +++ b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs @@ -102,7 +102,8 @@ namespace ICSharpCode.Decompiler.ILAst break; if (ldloc.Code != ILCode.Ldloc || ldarg.Code != ILCode.Ldarg) return false; - if (ldloc.Operand != var1 || !(storedField is FieldDefinition)) + storedField = GetFieldDefinition(storedField); + if (ldloc.Operand != var1 || storedField == null) return false; fieldToParameterMap[(FieldDefinition)storedField] = (ParameterDefinition)ldarg.Operand; } @@ -129,6 +130,22 @@ namespace ICSharpCode.Decompiler.ILAst return false; } + static FieldDefinition GetFieldDefinition(FieldReference field) + { + if (field != null && field.DeclaringType.IsGenericInstance) + return field.Resolve(); + else + return field as FieldDefinition; + } + + static MethodDefinition GetMethodDefinition(MethodReference method) + { + if (method != null && method.DeclaringType.IsGenericInstance) + return method.Resolve(); + else + return method as MethodDefinition; + } + bool SkipDummyBr(ILBlock method, ref int i) { ILLabel target; @@ -143,7 +160,7 @@ namespace ICSharpCode.Decompiler.ILAst bool MatchEnumeratorCreationNewObj(ILExpression expr, out MethodDefinition ctor) { // newobj(CurrentType/...::.ctor, ldc.i4(-2)) - ctor = expr.Operand as MethodDefinition; + ctor = null; if (expr.Code != ILCode.Newobj || expr.Arguments.Count != 1) return false; if (expr.Arguments[0].Code != ILCode.Ldc_I4) @@ -151,6 +168,7 @@ namespace ICSharpCode.Decompiler.ILAst int initialState = (int)expr.Arguments[0].Operand; if (!(initialState == -2 || initialState == 0)) return false; + ctor = GetMethodDefinition(expr.Operand as MethodReference); if (ctor == null || ctor.DeclaringType.DeclaringType != context.CurrentType) return false; return IsCompilerGeneratorEnumerator(ctor.DeclaringType); @@ -180,7 +198,7 @@ namespace ICSharpCode.Decompiler.ILAst foreach (ILNode node in method.Body) { if (stfldPattern.Match(node)) { - stateField = ((ILExpression)node).Operand as FieldDefinition; + stateField = GetFieldDefinition(((ILExpression)node).Operand as FieldReference); } } if (stateField == null) @@ -219,7 +237,7 @@ namespace ICSharpCode.Decompiler.ILAst if (method.Body.Count == 1) { // release builds directly return the current field if (returnFieldFromThisPattern.Match(method.Body[0])) { - currentField = ((ILExpression)method.Body[0]).Arguments[0].Operand as FieldDefinition; + currentField = GetFieldDefinition(((ILExpression)method.Body[0]).Arguments[0].Operand as FieldReference); } } else { StoreToVariable v = new StoreToVariable(new ILExpression(ILCode.Ldfld, ILExpression.AnyOperand, LoadFromArgument.This)); @@ -227,7 +245,7 @@ namespace ICSharpCode.Decompiler.ILAst int i = 1; if (SkipDummyBr(method, ref i) && i == method.Body.Count - 1) { if (new ILExpression(ILCode.Ret, null, new LoadFromVariable(v)).Match(method.Body[i])) { - currentField = ((ILExpression)method.Body[0]).Arguments[0].Operand as FieldDefinition; + currentField = GetFieldDefinition(((ILExpression)method.Body[0]).Arguments[0].Operand as FieldReference); } } } @@ -254,8 +272,8 @@ namespace ICSharpCode.Decompiler.ILAst foreach (ILNode node in method.Body) { if (mappingPattern.Match(node)) { ILExpression stfld = (ILExpression)node; - FieldDefinition storedField = stfld.Operand as FieldDefinition; - FieldDefinition loadedField = stfld.Arguments[1].Operand as FieldDefinition; + FieldDefinition storedField = GetFieldDefinition(stfld.Operand as FieldReference); + FieldDefinition loadedField = GetFieldDefinition(stfld.Arguments[1].Operand as FieldReference); if (storedField != null && loadedField != null) { ParameterDefinition mappedParameter; if (fieldToParameterMap.TryGetValue(loadedField, out mappedParameter)) @@ -304,7 +322,7 @@ namespace ICSharpCode.Decompiler.ILAst if (!finallyBody[finallyBody.Count - 1].Match(ILCode.Endfinally)) throw new YieldAnalysisFailedException(); - MethodDefinition mdef = call.Operand as MethodDefinition; + MethodDefinition mdef = GetMethodDefinition(call.Operand as MethodReference); if (mdef == null || finallyMethodToStateInterval.ContainsKey(mdef)) throw new YieldAnalysisFailedException(); finallyMethodToStateInterval.Add(mdef, interval); @@ -506,7 +524,7 @@ namespace ICSharpCode.Decompiler.ILAst case ILCode.Call: // in some cases (e.g. foreach over array) the C# compiler produces a finally method outside of try-finally blocks if (forDispose) { - MethodDefinition mdef = expr.Operand as MethodDefinition; + MethodDefinition mdef = GetMethodDefinition(expr.Operand as MethodReference); if (mdef == null || finallyMethodToStateInterval.ContainsKey(mdef)) throw new YieldAnalysisFailedException(); finallyMethodToStateInterval.Add(mdef, nodeRange.ToEnclosingInterval()); @@ -580,7 +598,7 @@ namespace ICSharpCode.Decompiler.ILAst case ILCode.Ldfld: if (Eval(expr.Arguments[0]).Type != SymbolicValueType.This) throw new YieldAnalysisFailedException(); - if (expr.Operand != stateField) + if (GetFieldDefinition(expr.Operand as FieldReference) != stateField) throw new YieldAnalysisFailedException(); return new SymbolicValue(SymbolicValueType.State); case ILCode.Ldloc: @@ -666,7 +684,11 @@ namespace ICSharpCode.Decompiler.ILAst // Ensure the fault block contains the call to Dispose(). if (!(faultBlock.Body.Count == 2 || faultBlock.Body.Count == 3)) throw new YieldAnalysisFailedException(); - if (!new ILExpression(ILCode.Call, disposeMethod, LoadFromArgument.This).Match(faultBlock.Body[0])) + MethodReference disposeMethodRef; + ILExpression disposeArg; + if (!faultBlock.Body[0].Match(ILCode.Call, out disposeMethodRef, out disposeArg)) + throw new YieldAnalysisFailedException(); + if (GetMethodDefinition(disposeMethodRef) != disposeMethod || !LoadFromArgument.This.Match(disposeArg)) throw new YieldAnalysisFailedException(); if (faultBlock.Body.Count == 3 && !faultBlock.Body[1].Match(ILCode.Nop)) throw new YieldAnalysisFailedException(); @@ -756,12 +778,12 @@ namespace ICSharpCode.Decompiler.ILAst ILExpression expr = body[pos] as ILExpression; if (expr != null && expr.Code == ILCode.Stfld && LoadFromArgument.This.Match(expr.Arguments[0])) { // Handle stores to 'state' or 'current' - if (expr.Operand == stateField) { + if (GetFieldDefinition(expr.Operand as FieldReference) == stateField) { if (expr.Arguments[1].Code != ILCode.Ldc_I4) throw new YieldAnalysisFailedException(); currentState = (int)expr.Arguments[1].Operand; stateChanges.Add(new SetState(newBody.Count, currentState)); - } else if (expr.Operand == currentField) { + } else if (GetFieldDefinition(expr.Operand as FieldReference) == currentField) { newBody.Add(new ILExpression(ILCode.YieldReturn, null, expr.Arguments[1])); } else { newBody.Add(body[pos]); @@ -792,7 +814,7 @@ namespace ICSharpCode.Decompiler.ILAst throw new YieldAnalysisFailedException(); } } else if (expr != null && expr.Code == ILCode.Call && expr.Arguments.Count == 1 && LoadFromArgument.This.Match(expr.Arguments[0])) { - MethodDefinition method = expr.Operand as MethodDefinition; + MethodDefinition method = GetMethodDefinition(expr.Operand as MethodReference); if (method == null) throw new YieldAnalysisFailedException(); Interval interval; @@ -855,7 +877,7 @@ namespace ICSharpCode.Decompiler.ILAst FieldReference stfld; List args; if (block.Body.Count > 0 && block.Body[0].Match(ILCode.Stfld, out stfld, out args)) { - if (stfld == stateField && LoadFromArgument.This.Match(args[0])) + if (GetFieldDefinition(stfld) == stateField && LoadFromArgument.This.Match(args[0])) block.Body.RemoveAt(0); } // Convert ret to endfinally @@ -873,7 +895,7 @@ namespace ICSharpCode.Decompiler.ILAst var fieldToLocalMap = new DefaultDictionary(f => new ILVariable { Name = f.Name, Type = f.FieldType }); foreach (ILNode node in newBody) { foreach (ILExpression expr in node.GetSelfAndChildrenRecursive()) { - FieldDefinition field = expr.Operand as FieldDefinition; + FieldDefinition field = GetFieldDefinition(expr.Operand as FieldReference); if (field != null) { switch (expr.Code) { case ILCode.Ldfld: