From 3e8158771dc3d1628f830267440015f26aa7b552 Mon Sep 17 00:00:00 2001 From: Ed Harvey Date: Fri, 22 Apr 2011 13:26:44 +1000 Subject: [PATCH 01/17] Corrected English in node text. 'Overrided by' should be 'Overriden by'. --- ILSpy/TreeNodes/Analyzer/AnalyzedPropertyOverridesTreeNode.cs | 2 +- ILSpy/TreeNodes/Analyzer/AnalyzerMethodOverridesTreeNode.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedPropertyOverridesTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedPropertyOverridesTreeNode.cs index 43601694a..4c800877c 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedPropertyOverridesTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedPropertyOverridesTreeNode.cs @@ -28,7 +28,7 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer public override object Text { - get { return "Overrided By"; } + get { return "Overriden By"; } } public override object Icon diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzerMethodOverridesTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzerMethodOverridesTreeNode.cs index 4200c78db..c046f45d8 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzerMethodOverridesTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzerMethodOverridesTreeNode.cs @@ -31,7 +31,7 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer public override object Text { - get { return "Overrided By"; } + get { return "Overriden By"; } } public override object Icon From fc9a914b62290e0c9582cd5cfc2293dc0f391730 Mon Sep 17 00:00:00 2001 From: Ed Harvey Date: Fri, 22 Apr 2011 14:21:54 +1000 Subject: [PATCH 02/17] Find method node returns property or event if accessors hidden. --- ILSpy/TreeNodes/AssemblyListTreeNode.cs | 71 ++++++++++++++----------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/ILSpy/TreeNodes/AssemblyListTreeNode.cs b/ILSpy/TreeNodes/AssemblyListTreeNode.cs index 12a4da03c..8dbab2fbf 100644 --- a/ILSpy/TreeNodes/AssemblyListTreeNode.cs +++ b/ILSpy/TreeNodes/AssemblyListTreeNode.cs @@ -34,11 +34,12 @@ namespace ICSharpCode.ILSpy.TreeNodes sealed class AssemblyListTreeNode : ILSpyTreeNode { readonly AssemblyList assemblyList; - - public AssemblyList AssemblyList { + + public AssemblyList AssemblyList + { get { return assemblyList; } } - + public AssemblyListTreeNode(AssemblyList assemblyList) { if (assemblyList == null) @@ -46,11 +47,12 @@ namespace ICSharpCode.ILSpy.TreeNodes this.assemblyList = assemblyList; BindToObservableCollection(assemblyList.assemblies); } - - public override object Text { + + public override object Text + { get { return assemblyList.ListName; } } - + void BindToObservableCollection(ObservableCollection collection) { this.Children.Clear(); @@ -75,7 +77,7 @@ namespace ICSharpCode.ILSpy.TreeNodes } }; } - + public override bool CanDrop(DragEventArgs e, int index) { e.Effects = DragDropEffects.Move; @@ -88,7 +90,7 @@ namespace ICSharpCode.ILSpy.TreeNodes return false; } } - + public override void Drop(DragEventArgs e, int index) { string[] files = e.Data.GetData(AssemblyTreeNode.DataFormat) as string[]; @@ -97,10 +99,10 @@ namespace ICSharpCode.ILSpy.TreeNodes if (files != null) { lock (assemblyList.assemblies) { var assemblies = (from file in files - where file != null - select assemblyList.OpenAssembly(file) into node - where node != null - select node).Distinct().ToList(); + where file != null + select assemblyList.OpenAssembly(file) into node + where node != null + select node).Distinct().ToList(); foreach (LoadedAssembly asm in assemblies) { int nodeIndex = assemblyList.assemblies.IndexOf(asm); if (nodeIndex < index) @@ -114,9 +116,9 @@ namespace ICSharpCode.ILSpy.TreeNodes } } } - - public Action Select = delegate {}; - + + public Action Select = delegate { }; + public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) { language.WriteCommentLine(output, "List: " + assemblyList.ListName); @@ -127,9 +129,9 @@ namespace ICSharpCode.ILSpy.TreeNodes asm.Decompile(language, output, options); } } - + #region Find*Node - + public AssemblyTreeNode FindAssemblyNode(AssemblyDefinition asm) { if (asm == null) @@ -141,7 +143,7 @@ namespace ICSharpCode.ILSpy.TreeNodes } return null; } - + public AssemblyTreeNode FindAssemblyNode(LoadedAssembly asm) { if (asm == null) @@ -153,7 +155,7 @@ namespace ICSharpCode.ILSpy.TreeNodes } return null; } - + /// /// Looks up the type node corresponding to the type definition. /// Returns null if no matching node is found. @@ -176,12 +178,12 @@ namespace ICSharpCode.ILSpy.TreeNodes } return null; } - + /// /// Looks up the method node corresponding to the method definition. /// Returns null if no matching node is found. /// - public MethodTreeNode FindMethodNode(MethodDefinition def) + public SharpTreeNode FindMethodNode(MethodDefinition def) { if (def == null) return null; @@ -195,16 +197,25 @@ namespace ICSharpCode.ILSpy.TreeNodes foreach (var p in typeNode.Children.OfType()) { if (p.IsHidden) continue; - // method might be a child or a property or events - p.EnsureLazyChildren(); - methodNode = p.Children.OfType().FirstOrDefault(m => m.MethodDefinition == def && !m.IsHidden); - if (methodNode != null) - return methodNode; + + // method might be a child of a property or event + if (p is PropertyTreeNode || p is EventTreeNode) { + p.EnsureLazyChildren(); + methodNode = p.Children.OfType().FirstOrDefault(m => m.MethodDefinition == def); + if (methodNode != null) { + /// If the requested method is a property or event accessor, and accessors are + /// hidden in the UI, then return the owning property or event. + if (methodNode.IsHidden) + return p; + else + return methodNode; + } + } } - + return null; } - + /// /// Looks up the field node corresponding to the field definition. /// Returns null if no matching node is found. @@ -219,7 +230,7 @@ namespace ICSharpCode.ILSpy.TreeNodes typeNode.EnsureLazyChildren(); return typeNode.Children.OfType().FirstOrDefault(m => m.FieldDefinition == def && !m.IsHidden); } - + /// /// Looks up the property node corresponding to the property definition. /// Returns null if no matching node is found. @@ -234,7 +245,7 @@ namespace ICSharpCode.ILSpy.TreeNodes typeNode.EnsureLazyChildren(); return typeNode.Children.OfType().FirstOrDefault(m => m.PropertyDefinition == def && !m.IsHidden); } - + /// /// Looks up the event node corresponding to the event definition. /// Returns null if no matching node is found. From 1aaa720cc4c8b1380201f2060b940bc66d4e8a3a Mon Sep 17 00:00:00 2001 From: Ed Harvey Date: Fri, 22 Apr 2011 14:24:57 +1000 Subject: [PATCH 03/17] Constrain where-used searches to accessibility domain. --- ILSpy/ILSpy.csproj | 1 + .../Analyzer/AnalyzedFieldAccessNode.cs | 66 +++-- .../Analyzer/AnalyzedMethodUsedByTreeNode.cs | 62 ++--- .../Analyzer/ScopedWhereUsedAnalyzer.cs | 250 ++++++++++++++++++ 4 files changed, 309 insertions(+), 70 deletions(-) create mode 100644 ILSpy/TreeNodes/Analyzer/ScopedWhereUsedAnalyzer.cs diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index 5edcea10e..115c6a6a7 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -132,6 +132,7 @@ + diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedFieldAccessNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedFieldAccessNode.cs index c9a0a6fb2..3375048c5 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedFieldAccessNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedFieldAccessNode.cs @@ -18,31 +18,33 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer readonly bool showWrites; // true: show writes; false: show read access readonly FieldDefinition analyzedField; readonly ThreadingSupport threading; - + public AnalyzedFieldAccessNode(FieldDefinition analyzedField, bool showWrites) { if (analyzedField == null) throw new ArgumentNullException("analyzedField"); - + this.analyzedField = analyzedField; this.showWrites = showWrites; this.threading = new ThreadingSupport(); this.LazyLoading = true; } - - public override object Text { + + public override object Text + { get { return showWrites ? "Assigned By" : "Read By"; } } - - public override object Icon { + + public override object Icon + { get { return Images.Search; } } - + protected override void LoadChildren() { threading.LoadChildren(this, FetchChildren); } - + protected override void OnCollapsing() { if (threading.IsRunning) { @@ -51,44 +53,36 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer this.Children.Clear(); } } - + IEnumerable FetchChildren(CancellationToken ct) { - return FindReferences(MainWindow.Instance.CurrentAssemblyList.GetAssemblies(), ct); - } - - IEnumerable FindReferences(IEnumerable assemblies, CancellationToken ct) - { - assemblies = assemblies.Where(asm => asm.AssemblyDefinition != null); - // use parallelism only on the assembly level (avoid locks within Cecil) - return assemblies.AsParallel().WithCancellation(ct).SelectMany((LoadedAssembly asm) => FindReferences(asm, ct)); + var analyzer = new ScopedWhereUsedScopeAnalyzer(analyzedField, FindReferencesInType); + return analyzer.PerformAnalysis(ct); } - - IEnumerable FindReferences(LoadedAssembly asm, CancellationToken ct) + + IEnumerable FindReferencesInType(TypeDefinition type) { string name = analyzedField.Name; - foreach (TypeDefinition type in TreeTraversal.PreOrder(asm.AssemblyDefinition.MainModule.Types, t => t.NestedTypes)) { - ct.ThrowIfCancellationRequested(); - foreach (MethodDefinition method in type.Methods) { - ct.ThrowIfCancellationRequested(); - bool found = false; - if (!method.HasBody) - continue; - foreach (Instruction instr in method.Body.Instructions) { - if (CanBeReference(instr.OpCode.Code)) { - FieldReference fr = instr.Operand as FieldReference; - if (fr != null && fr.Name == name && Helpers.IsReferencedBy(analyzedField.DeclaringType, fr.DeclaringType) && fr.Resolve() == analyzedField) { - found = true; - break; - } + string declTypeName = analyzedField.DeclaringType.FullName; + + foreach (MethodDefinition method in type.Methods) { + bool found = false; + if (!method.HasBody) + continue; + foreach (Instruction instr in method.Body.Instructions) { + if (CanBeReference(instr.OpCode.Code)) { + FieldReference fr = instr.Operand as FieldReference; + if (fr != null && fr.Name == name && Helpers.IsReferencedBy(analyzedField.DeclaringType, fr.DeclaringType) && fr.Resolve() == analyzedField) { + found = true; + break; } } - if (found) - yield return new AnalyzedMethodTreeNode(method); } + if (found) + yield return new AnalyzedMethodTreeNode(method); } } - + bool CanBeReference(Code code) { switch (code) { diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsedByTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsedByTreeNode.cs index fca078f9d..f89081064 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsedByTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsedByTreeNode.cs @@ -32,30 +32,32 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer { MethodDefinition analyzedMethod; ThreadingSupport threading; - + public AnalyzedMethodUsedByTreeNode(MethodDefinition analyzedMethod) { if (analyzedMethod == null) throw new ArgumentNullException("analyzedMethod"); - + this.analyzedMethod = analyzedMethod; this.threading = new ThreadingSupport(); this.LazyLoading = true; } - - public override object Text { + + public override object Text + { get { return "Used By"; } } - - public override object Icon { + + public override object Icon + { get { return Images.Search; } } - + protected override void LoadChildren() { threading.LoadChildren(this, FetchChildren); } - + protected override void OnCollapsing() { if (threading.IsRunning) { @@ -64,39 +66,31 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer this.Children.Clear(); } } - + IEnumerable FetchChildren(CancellationToken ct) { - return FindReferences(MainWindow.Instance.CurrentAssemblyList.GetAssemblies(), ct); - } - - IEnumerable FindReferences(IEnumerable assemblies, CancellationToken ct) - { - assemblies = assemblies.Where(asm => asm.AssemblyDefinition != null); - // use parallelism only on the assembly level (avoid locks within Cecil) - return assemblies.AsParallel().WithCancellation(ct).SelectMany((LoadedAssembly asm) => FindReferences(asm, ct)); + ScopedWhereUsedScopeAnalyzer analyzer; + + analyzer = new ScopedWhereUsedScopeAnalyzer(analyzedMethod, FindReferencesInType); + return analyzer.PerformAnalysis(ct); } - - IEnumerable FindReferences(LoadedAssembly asm, CancellationToken ct) + + IEnumerable FindReferencesInType(TypeDefinition type) { string name = analyzedMethod.Name; - foreach (TypeDefinition type in TreeTraversal.PreOrder(asm.AssemblyDefinition.MainModule.Types, t => t.NestedTypes)) { - ct.ThrowIfCancellationRequested(); - foreach (MethodDefinition method in type.Methods) { - ct.ThrowIfCancellationRequested(); - bool found = false; - if (!method.HasBody) - continue; - foreach (Instruction instr in method.Body.Instructions) { - MethodReference mr = instr.Operand as MethodReference; - if (mr != null && mr.Name == name && Helpers.IsReferencedBy(analyzedMethod.DeclaringType, mr.DeclaringType) && mr.Resolve() == analyzedMethod) { - found = true; - break; - } + foreach (MethodDefinition method in type.Methods) { + bool found = false; + if (!method.HasBody) + continue; + foreach (Instruction instr in method.Body.Instructions) { + MethodReference mr = instr.Operand as MethodReference; + if (mr != null && mr.Name == name && Helpers.IsReferencedBy(analyzedMethod.DeclaringType, mr.DeclaringType) && mr.Resolve() == analyzedMethod) { + found = true; + break; } - if (found) - yield return new AnalyzedMethodTreeNode(method); } + if (found) + yield return new AnalyzedMethodTreeNode(method); } } } diff --git a/ILSpy/TreeNodes/Analyzer/ScopedWhereUsedAnalyzer.cs b/ILSpy/TreeNodes/Analyzer/ScopedWhereUsedAnalyzer.cs new file mode 100644 index 000000000..281da3a21 --- /dev/null +++ b/ILSpy/TreeNodes/Analyzer/ScopedWhereUsedAnalyzer.cs @@ -0,0 +1,250 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Mono.Cecil; +using ICSharpCode.NRefactory.Utils; +using ICSharpCode.TreeView; + + +namespace ICSharpCode.ILSpy.TreeNodes.Analyzer +{ + /// + /// Determines the accessibility domain of a member for where-used analysis. + /// + internal class ScopedWhereUsedScopeAnalyzer + { + private AssemblyDefinition assemblyScope; + private TypeDefinition typeScope; + + private Accessibility memberAccessibility = Accessibility.Public; + private Accessibility typeAccessibility = Accessibility.Public; + private Func> typeAnalysisFunction; + + private ScopedWhereUsedScopeAnalyzer(TypeDefinition type, Func> typeAnalysisFunction) + { + this.typeScope = type; + this.assemblyScope = type.Module.Assembly; + this.typeAnalysisFunction = typeAnalysisFunction; + } + + public ScopedWhereUsedScopeAnalyzer(MethodDefinition method, Func> typeAnalysisFunction) + : this(method.DeclaringType, typeAnalysisFunction) + { + switch (method.Attributes & MethodAttributes.MemberAccessMask) { + case MethodAttributes.Private: + default: + memberAccessibility = Accessibility.Private; + break; + case MethodAttributes.FamANDAssem: + memberAccessibility = Accessibility.FamilyAndInternal; + break; + case MethodAttributes.Family: + memberAccessibility = Accessibility.Family; + break; + case MethodAttributes.Assembly: + memberAccessibility = Accessibility.Internal; + break; + case MethodAttributes.FamORAssem: + memberAccessibility = Accessibility.FamilyOrInternal; + break; + case MethodAttributes.Public: + memberAccessibility = Accessibility.Public; + break; + } + } + + public ScopedWhereUsedScopeAnalyzer(FieldDefinition field, Func> typeAnalysisFunction) + : this(field.DeclaringType, typeAnalysisFunction) + { + switch (field.Attributes & FieldAttributes.FieldAccessMask) { + case FieldAttributes.Private: + default: + memberAccessibility = Accessibility.Private; + break; + case FieldAttributes.FamANDAssem: + memberAccessibility = Accessibility.FamilyAndInternal; + break; + case FieldAttributes.Assembly: + memberAccessibility = Accessibility.Internal; + break; + case FieldAttributes.Family: + memberAccessibility = Accessibility.Family; + break; + case FieldAttributes.FamORAssem: + memberAccessibility = Accessibility.FamilyOrInternal; + break; + case FieldAttributes.Public: + memberAccessibility = Accessibility.Public; + break; + } + } + + public IEnumerable PerformAnalysis(CancellationToken ct) + { + if (memberAccessibility == Accessibility.Private) { + return FindReferencesInTypeScope(ct); + } + + DetermineTypeAccessibility(); + + if (typeAccessibility == Accessibility.Private) { + return FindReferencesInTypeScope(ct); + } + + + if (memberAccessibility == Accessibility.Internal || + memberAccessibility == Accessibility.FamilyAndInternal || + typeAccessibility == Accessibility.Internal || + typeAccessibility == Accessibility.FamilyAndInternal) + return FindReferencesInAssemblyAndFriends(ct); + + return FindReferencesGlobal(ct); + } + + private void DetermineTypeAccessibility() + { + while (typeScope.IsNested) { + Accessibility accessibility = GetNestedTypeAccessibility(typeScope); + if ((int)typeAccessibility > (int)accessibility) { + typeAccessibility = accessibility; + if (typeAccessibility == Accessibility.Private) + return; + } + typeScope = typeScope.DeclaringType; + } + + if (typeScope.IsNotPublic && + ((int)typeAccessibility > (int)Accessibility.Internal)) { + typeAccessibility = Accessibility.Internal; + } + } + + private Accessibility GetNestedTypeAccessibility(TypeDefinition type) + { + Accessibility result; + switch (type.Attributes & TypeAttributes.VisibilityMask) { + case TypeAttributes.NestedPublic: + result = Accessibility.Public; + break; + case TypeAttributes.NestedPrivate: + result = Accessibility.Private; + break; + case TypeAttributes.NestedFamily: + result = Accessibility.Family; + break; + case TypeAttributes.NestedAssembly: + result = Accessibility.Internal; + break; + case TypeAttributes.NestedFamANDAssem: + result = Accessibility.FamilyAndInternal; + break; + case TypeAttributes.NestedFamORAssem: + result = Accessibility.FamilyOrInternal; + break; + default: + throw new InvalidOperationException(); + } + return result; + } + + /// + /// The effective accessibility of a member + /// + private enum Accessibility + { + Private, + FamilyAndInternal, + Internal, + Family, + FamilyOrInternal, + Public + } + + + IEnumerable FindReferencesInAssemblyAndFriends(CancellationToken ct) + { + var assemblies = GetAssemblyAndAnyFriends(assemblyScope, ct); + // use parallelism only on the assembly level (avoid locks within Cecil) + return assemblies.AsParallel().WithCancellation(ct).SelectMany((AssemblyDefinition a) => FindReferencesInAssembly(a, ct)); + } + + IEnumerable FindReferencesGlobal(CancellationToken ct) + { + var assemblies = GetReferencingAssemblies(assemblyScope, ct); + // use parallelism only on the assembly level (avoid locks within Cecil) + return assemblies.AsParallel().WithCancellation(ct).SelectMany((AssemblyDefinition asm) => FindReferencesInAssembly(asm, ct)); + } + + IEnumerable FindReferencesInAssembly(AssemblyDefinition asm, CancellationToken ct) + { + foreach (TypeDefinition type in TreeTraversal.PreOrder(asm.MainModule.Types, t => t.NestedTypes)) { + ct.ThrowIfCancellationRequested(); + foreach (var result in typeAnalysisFunction(type)) { + ct.ThrowIfCancellationRequested(); + yield return result; + } + } + } + + IEnumerable FindReferencesInTypeScope(CancellationToken ct) + { + foreach (TypeDefinition type in TreeTraversal.PreOrder(typeScope, t => t.NestedTypes)) { + ct.ThrowIfCancellationRequested(); + foreach (var result in typeAnalysisFunction(type)) { + ct.ThrowIfCancellationRequested(); + yield return result; + } + } + } + + IEnumerable GetReferencingAssemblies(AssemblyDefinition asm, CancellationToken ct) + { + yield return asm; + + string requiredAssemblyFullName = asm.FullName; + + IEnumerable assemblies = MainWindow.Instance.CurrentAssemblyList.GetAssemblies().Where(assy => assy.AssemblyDefinition != null); + + foreach (var assembly in assemblies) { + ct.ThrowIfCancellationRequested(); + bool found = false; + foreach (var reference in assembly.AssemblyDefinition.MainModule.AssemblyReferences) { + if (requiredAssemblyFullName == reference.FullName) { + found = true; + break; + } + } + if (found) + yield return assembly.AssemblyDefinition; + } + } + + IEnumerable GetAssemblyAndAnyFriends(AssemblyDefinition asm, CancellationToken ct) + { + yield return asm; + + if (asm.HasCustomAttributes) { + var attributes = asm.CustomAttributes + .Where(attr => attr.AttributeType.FullName == "System.Runtime.CompilerServices.InternalsVisibleToAttribute"); + var friendAssemblies = new HashSet(); + foreach (var attribute in attributes) { + string assemblyName = attribute.ConstructorArguments[0].Value as string; + assemblyName = assemblyName.Split(',')[0]; // strip off any public key info + friendAssemblies.Add(assemblyName); + } + + if (friendAssemblies.Count > 0) { + IEnumerable assemblies = MainWindow.Instance.CurrentAssemblyList.GetAssemblies(); + + foreach (var assembly in assemblies) { + ct.ThrowIfCancellationRequested(); + if (friendAssemblies.Contains(assembly.ShortName)) { + yield return assembly.AssemblyDefinition; + } + } + } + } + } + } +} From e86aee752dfc594fc675be407aab965b71840c9b Mon Sep 17 00:00:00 2001 From: Ed Harvey Date: Fri, 22 Apr 2011 15:27:24 +1000 Subject: [PATCH 04/17] Added Event analysis. --- .../Ast/TypesHierarchyHelpers.cs | 52 +++++++++++ ILSpy/ILSpy.csproj | 3 + .../Analyzer/AnalyzeContextMenuEntry.cs | 6 +- .../AnalyzedEventAccessorsTreeNode.cs | 58 ++++++++++++ .../AnalyzedEventOverridesTreeNode.cs | 93 +++++++++++++++++++ .../Analyzer/AnalyzedEventTreeNode.cs | 81 ++++++++++++++++ ILSpy/TreeNodes/EventTreeNode.cs | 27 +++++- 7 files changed, 314 insertions(+), 6 deletions(-) create mode 100644 ILSpy/TreeNodes/Analyzer/AnalyzedEventAccessorsTreeNode.cs create mode 100644 ILSpy/TreeNodes/Analyzer/AnalyzedEventOverridesTreeNode.cs create mode 100644 ILSpy/TreeNodes/Analyzer/AnalyzedEventTreeNode.cs diff --git a/ICSharpCode.Decompiler/Ast/TypesHierarchyHelpers.cs b/ICSharpCode.Decompiler/Ast/TypesHierarchyHelpers.cs index c9105d4e7..5104ea7ba 100644 --- a/ICSharpCode.Decompiler/Ast/TypesHierarchyHelpers.cs +++ b/ICSharpCode.Decompiler/Ast/TypesHierarchyHelpers.cs @@ -61,6 +61,14 @@ namespace ICSharpCode.Decompiler.Ast return FindBaseProperties(childProperty).Any(m => m == parentProperty); } + public static bool IsBaseEvent(EventDefinition parentEvent, EventDefinition childEvent) + { + if (parentEvent.Name != childEvent.Name) + return false; + + return FindBaseEvents(childEvent).Any(m => m == parentEvent); + } + public static IEnumerable FindBaseMethods(MethodDefinition method) { if (method == null) @@ -97,6 +105,25 @@ namespace ICSharpCode.Decompiler.Ast } + public static IEnumerable FindBaseEvents(EventDefinition eventDef) + { + if (eventDef == null) + throw new ArgumentNullException("eventDef"); + + var typeContext = CreateGenericContext(eventDef.DeclaringType); + var gEvent = typeContext.ApplyTo(eventDef); + + foreach (var baseType in BaseTypes(eventDef.DeclaringType)) + foreach (var baseEvent in baseType.Item.Events) + if (MatchEvent(baseType.ApplyTo(baseEvent), gEvent) && IsVisbleFrom(baseEvent, eventDef)) { + yield return baseEvent; + var anyEventAccessor = baseEvent.AddMethod ?? baseEvent.RemoveMethod; + if (!(anyEventAccessor.IsNewSlot ^ anyEventAccessor.IsVirtual)) + yield break; + } + + } + private static bool IsVisbleFrom(MethodDefinition baseCandidate, MethodDefinition method) { if (baseCandidate.IsPrivate) @@ -115,6 +142,15 @@ namespace ICSharpCode.Decompiler.Ast return false; } + private static bool IsVisbleFrom(EventDefinition baseCandidate, EventDefinition eventDef) + { + if (baseCandidate.AddMethod != null && eventDef.AddMethod != null && IsVisbleFrom(baseCandidate.AddMethod, eventDef.AddMethod)) + return true; + if (baseCandidate.RemoveMethod != null && eventDef.RemoveMethod != null && IsVisbleFrom(baseCandidate.RemoveMethod, eventDef.RemoveMethod)) + return true; + return false; + } + private static bool MatchMethod(GenericContext candidate, GenericContext method) { var mCandidate = candidate.Item; @@ -172,6 +208,22 @@ namespace ICSharpCode.Decompiler.Ast return true; } + private static bool MatchEvent(GenericContext candidate, GenericContext ev) + { + var mCandidate = candidate.Item; + var mEvent = ev.Item; + if (mCandidate.Name != mEvent.Name) + return false; + + if ((mCandidate.AddMethod ?? mCandidate.RemoveMethod).HasOverrides) + return false; + + if (!IsSameType(candidate.ResolveWithContext(mCandidate.EventType), ev.ResolveWithContext(mEvent.EventType))) + return false; + + return true; + } + private static bool MatchParameters(GenericContext baseParameterType, GenericContext parameterType) { var baseParam = baseParameterType.ResolveWithContext(baseParameterType.Item.ParameterType); diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index 115c6a6a7..e04a90c6d 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -131,6 +131,9 @@ + + + diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzeContextMenuEntry.cs b/ILSpy/TreeNodes/Analyzer/AnalyzeContextMenuEntry.cs index c7c4e0266..fa2c5bf29 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzeContextMenuEntry.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzeContextMenuEntry.cs @@ -36,7 +36,8 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer foreach (IMemberTreeNode node in selectedNodes) { if (!(node.Member is FieldDefinition || node.Member is MethodDefinition - || Analyzer.AnalyzedPropertyTreeNode.CanShow(node.Member))) + || Analyzer.AnalyzedPropertyTreeNode.CanShow(node.Member) + || Analyzer.AnalyzedEventTreeNode.CanShow(node.Member))) return false; } return true; @@ -56,6 +57,9 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer var propertyAnalyzer = Analyzer.AnalyzedPropertyTreeNode.TryCreateAnalyzer(node.Member); if(propertyAnalyzer != null) MainWindow.Instance.AddToAnalyzer(propertyAnalyzer); + var eventAnalyzer = Analyzer.AnalyzedEventTreeNode.TryCreateAnalyzer(node.Member); + if (eventAnalyzer != null) + MainWindow.Instance.AddToAnalyzer(eventAnalyzer); } } } diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedEventAccessorsTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedEventAccessorsTreeNode.cs new file mode 100644 index 000000000..8bf0285fb --- /dev/null +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedEventAccessorsTreeNode.cs @@ -0,0 +1,58 @@ +using System; +using Mono.Cecil; + +namespace ICSharpCode.ILSpy.TreeNodes.Analyzer +{ + /// + /// Description of AnalyzedEventAccessorsTreeNode. + /// + public class AnalyzedEventAccessorsTreeNode : AnalyzerTreeNode + { + EventDefinition analyzedEvent; + + public AnalyzedEventAccessorsTreeNode(EventDefinition analyzedEvent) + { + if (analyzedEvent == null) + throw new ArgumentNullException("analyzedEvent"); + this.analyzedEvent = analyzedEvent; + + if (analyzedEvent.AddMethod != null) + this.Children.Add(new AnalyzedEventAccessorTreeNode(analyzedEvent.AddMethod, "add")); + if (analyzedEvent.RemoveMethod != null) + this.Children.Add(new AnalyzedEventAccessorTreeNode(analyzedEvent.RemoveMethod, "remove")); + foreach (var accessor in analyzedEvent.OtherMethods) + this.Children.Add(new AnalyzedEventAccessorTreeNode(accessor, null)); + } + + public override object Icon + { + get { return Images.Search; } + } + + public override object Text + { + get { return "Accessors"; } + } + + public static bool CanShow(EventDefinition property) + { + return !MainWindow.Instance.CurrentLanguage.ShowMember(property.AddMethod ?? property.RemoveMethod); + } + + class AnalyzedEventAccessorTreeNode : AnalyzedMethodTreeNode + { + string name; + + public AnalyzedEventAccessorTreeNode(MethodDefinition analyzedMethod, string name) + : base(analyzedMethod) + { + this.name = name; + } + + public override object Text + { + get { return name ?? base.Text; } + } + } + } +} diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedEventOverridesTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedEventOverridesTreeNode.cs new file mode 100644 index 000000000..f406b45e0 --- /dev/null +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedEventOverridesTreeNode.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using ICSharpCode.Decompiler.Ast; +using ICSharpCode.NRefactory.Utils; +using ICSharpCode.TreeView; +using Mono.Cecil; + +namespace ICSharpCode.ILSpy.TreeNodes.Analyzer +{ + class AnalyzedEventOverridesTreeNode : AnalyzerTreeNode + { + readonly EventDefinition analyzedEvent; + readonly ThreadingSupport threading; + + public AnalyzedEventOverridesTreeNode(EventDefinition analyzedEvent) + { + if (analyzedEvent == null) + throw new ArgumentNullException("analyzedEvent"); + + this.analyzedEvent = analyzedEvent; + this.threading = new ThreadingSupport(); + this.LazyLoading = true; + } + + public override object Text + { + get { return "Overriden By"; } + } + + public override object Icon + { + get { return Images.Search; } + } + + protected override void LoadChildren() + { + threading.LoadChildren(this, FetchChildren); + } + + protected override void OnCollapsing() + { + if (threading.IsRunning) { + this.LazyLoading = true; + threading.Cancel(); + this.Children.Clear(); + } + } + + IEnumerable FetchChildren(CancellationToken ct) + { + return FindReferences(MainWindow.Instance.CurrentAssemblyList.GetAssemblies(), ct); + } + + IEnumerable FindReferences(IEnumerable assemblies, CancellationToken ct) + { + assemblies = assemblies.Where(asm => asm.AssemblyDefinition != null); + // use parallelism only on the assembly level (avoid locks within Cecil) + return assemblies.AsParallel().WithCancellation(ct).SelectMany((LoadedAssembly asm) => FindReferences(asm, ct)); + } + + IEnumerable FindReferences(LoadedAssembly asm, CancellationToken ct) + { + string asmName = asm.AssemblyDefinition.Name.Name; + string name = analyzedEvent.Name; + string declTypeName = analyzedEvent.DeclaringType.FullName; + foreach (TypeDefinition type in TreeTraversal.PreOrder(asm.AssemblyDefinition.MainModule.Types, t => t.NestedTypes)) { + ct.ThrowIfCancellationRequested(); + + if (!TypesHierarchyHelpers.IsBaseType(analyzedEvent.DeclaringType, type, resolveTypeArguments: false)) + continue; + + foreach (EventDefinition eventDef in type.Events) { + ct.ThrowIfCancellationRequested(); + + if (TypesHierarchyHelpers.IsBaseEvent(analyzedEvent, eventDef)) { + MethodDefinition anyAccessor = eventDef.AddMethod ?? eventDef.RemoveMethod; + bool hidesParent = !anyAccessor.IsVirtual ^ anyAccessor.IsNewSlot; + yield return new AnalyzedEventTreeNode(eventDef, hidesParent ? "(hides) " : ""); + } + } + } + } + + public static bool CanShowAnalyzer(EventDefinition property) + { + var accessor = property.AddMethod ?? property.RemoveMethod; + return accessor.IsVirtual && !accessor.IsFinal && !accessor.DeclaringType.IsInterface; + } + } +} diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedEventTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedEventTreeNode.cs new file mode 100644 index 000000000..e2db2aa67 --- /dev/null +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedEventTreeNode.cs @@ -0,0 +1,81 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using Mono.Cecil; +using ICSharpCode.Decompiler; + +namespace ICSharpCode.ILSpy.TreeNodes.Analyzer +{ + class AnalyzedEventTreeNode : AnalyzerTreeNode + { + EventDefinition analyzedEvent; + string prefix; + + public AnalyzedEventTreeNode(EventDefinition analyzedEvent, string prefix = "") + { + if (analyzedEvent == null) + throw new ArgumentNullException("analyzedMethod"); + this.analyzedEvent = analyzedEvent; + this.prefix = prefix; + this.LazyLoading = true; + } + + public override object Icon { + get { return EventTreeNode.GetIcon(analyzedEvent); } + } + + public override object Text { + get { + // TODO: This way of formatting is not suitable for events which explicitly implement interfaces. + return prefix + Language.TypeToString(analyzedEvent.DeclaringType, true) + "." + EventTreeNode.GetText(analyzedEvent, Language); } + } + + public override void ActivateItem(System.Windows.RoutedEventArgs e) + { + e.Handled = true; + MainWindow.Instance.JumpToReference(analyzedEvent); + } + + protected override void LoadChildren() + { + if(AnalyzedEventAccessorsTreeNode.CanShow(analyzedEvent)) + this.Children.Add(new AnalyzedEventAccessorsTreeNode(analyzedEvent)); + if (AnalyzedEventOverridesTreeNode.CanShowAnalyzer(analyzedEvent)) + this.Children.Add(new AnalyzedEventOverridesTreeNode(analyzedEvent)); + } + + public static AnalyzerTreeNode TryCreateAnalyzer(MemberReference member) + { + if (CanShow(member)) + return new AnalyzedEventTreeNode(member as EventDefinition); + else + return null; + } + + public static bool CanShow(MemberReference member) + { + var property = member as EventDefinition; + if (property == null) + return false; + + return AnalyzedEventAccessorsTreeNode.CanShow(property) + || AnalyzedEventOverridesTreeNode.CanShowAnalyzer(property); + } + } +} diff --git a/ILSpy/TreeNodes/EventTreeNode.cs b/ILSpy/TreeNodes/EventTreeNode.cs index 94cba230e..55e6432e9 100644 --- a/ILSpy/TreeNodes/EventTreeNode.cs +++ b/ILSpy/TreeNodes/EventTreeNode.cs @@ -47,17 +47,33 @@ namespace ICSharpCode.ILSpy.TreeNodes } } - public EventDefinition EventDefinition { + public EventDefinition EventDefinition + { get { return ev; } } - public override object Text { - get { return HighlightSearchMatch(ev.Name, " : " + this.Language.TypeToString(ev.EventType, false, ev)); } + public override object Text + { + get { return GetText(ev, this.Language); } + } + + public static object GetText(EventDefinition eventDef, Language language) + { + return HighlightSearchMatch(eventDef.Name, " : " + language.TypeToString(eventDef.EventType, false, eventDef)); } public override object Icon { - get { return Images.GetIcon(MemberIcon.Event, GetOverlayIcon(ev.AddMethod.Attributes), ev.AddMethod.IsStatic); } + get { return GetIcon(ev); } + } + + public static object GetIcon(EventDefinition eventDef) + { + MethodDefinition accessor = eventDef.AddMethod ?? eventDef.RemoveMethod; + if (accessor != null) + return Images.GetIcon(MemberIcon.Event, GetOverlayIcon(eventDef.AddMethod.Attributes), eventDef.AddMethod.IsStatic); + else + return Images.GetIcon(MemberIcon.Event, AccessOverlayIcon.Public, false); } private static AccessOverlayIcon GetOverlayIcon(MethodAttributes methodAttributes) @@ -91,7 +107,8 @@ namespace ICSharpCode.ILSpy.TreeNodes language.DecompileEvent(ev, output, options); } - MemberReference IMemberTreeNode.Member { + MemberReference IMemberTreeNode.Member + { get { return ev; } } } From 7f5b04d8e05c90af4224ced69a768b7c31c39dfc Mon Sep 17 00:00:00 2001 From: Ed Harvey Date: Fri, 22 Apr 2011 16:35:12 +1000 Subject: [PATCH 05/17] Add support for shadowing events. --- ICSharpCode.Decompiler/Ast/AstBuilder.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/Ast/AstBuilder.cs b/ICSharpCode.Decompiler/Ast/AstBuilder.cs index 878c743e9..1429aa3c3 100644 --- a/ICSharpCode.Decompiler/Ast/AstBuilder.cs +++ b/ICSharpCode.Decompiler/Ast/AstBuilder.cs @@ -880,8 +880,12 @@ namespace ICSharpCode.Decompiler.Ast astEvent.RemoveAccessor.WithAnnotation(methodMapping); } - return astEvent; + MethodDefinition accessor = eventDef.AddMethod ?? eventDef.RemoveMethod; + if (accessor.IsVirtual ^ !accessor.IsNewSlot) { + if (TypesHierarchyHelpers.FindBaseMethods(accessor).Any()) + astEvent.Modifiers |= Modifiers.New; } + return astEvent; } public bool DecompileMethodBodies { get; set; } From b35cd2f7ba11d9bf77a702c9343ecc63c09c7e82 Mon Sep 17 00:00:00 2001 From: Ed Harvey Date: Fri, 22 Apr 2011 17:27:20 +1000 Subject: [PATCH 06/17] Fix incorrect merge in 7f5b04d Add support for shadowing events. --- ICSharpCode.Decompiler/Ast/AstBuilder.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/Ast/AstBuilder.cs b/ICSharpCode.Decompiler/Ast/AstBuilder.cs index 1429aa3c3..6993e2ff1 100644 --- a/ICSharpCode.Decompiler/Ast/AstBuilder.cs +++ b/ICSharpCode.Decompiler/Ast/AstBuilder.cs @@ -884,8 +884,9 @@ namespace ICSharpCode.Decompiler.Ast if (accessor.IsVirtual ^ !accessor.IsNewSlot) { if (TypesHierarchyHelpers.FindBaseMethods(accessor).Any()) astEvent.Modifiers |= Modifiers.New; - } + } return astEvent; + } } public bool DecompileMethodBodies { get; set; } From 93a12a9b82c90364f9744fc3622b281e1a23c857 Mon Sep 17 00:00:00 2001 From: Ed Harvey Date: Fri, 22 Apr 2011 17:31:35 +1000 Subject: [PATCH 07/17] Added tests for shadowed events. --- .../Tests/Types/S_TypeMemberDeclarations.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/ICSharpCode.Decompiler/Tests/Types/S_TypeMemberDeclarations.cs b/ICSharpCode.Decompiler/Tests/Types/S_TypeMemberDeclarations.cs index 46b417cdb..943b3bc7c 100644 --- a/ICSharpCode.Decompiler/Tests/Types/S_TypeMemberDeclarations.cs +++ b/ICSharpCode.Decompiler/Tests/Types/S_TypeMemberDeclarations.cs @@ -668,4 +668,22 @@ namespace MethodHideGeneric2 { } } + //$$ EventHiding + namespace EventHiding + { + public class A + { + public virtual event EventHandler E; + public event EventHandler F; + } + public class B : A + { + public new event EventHandler E; + public new event EventHandler F; + } + public class C : B + { + public override event EventHandler E; + } + } } From 33b35a45af7b912f3820b72aa5a24e164ebd8203 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Fri, 22 Apr 2011 13:47:17 +0200 Subject: [PATCH 08/17] Fix EventHiding unit test. --- .../Tests/Types/S_TypeMemberDeclarations.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/ICSharpCode.Decompiler/Tests/Types/S_TypeMemberDeclarations.cs b/ICSharpCode.Decompiler/Tests/Types/S_TypeMemberDeclarations.cs index 943b3bc7c..401bfab61 100644 --- a/ICSharpCode.Decompiler/Tests/Types/S_TypeMemberDeclarations.cs +++ b/ICSharpCode.Decompiler/Tests/Types/S_TypeMemberDeclarations.cs @@ -668,22 +668,22 @@ namespace MethodHideGeneric2 { } } - //$$ EventHiding - namespace EventHiding +} +//$$ EventHiding +namespace EventHiding +{ + public class A { - public class A - { - public virtual event EventHandler E; - public event EventHandler F; - } - public class B : A - { - public new event EventHandler E; - public new event EventHandler F; - } - public class C : B - { - public override event EventHandler E; - } + public virtual event EventHandler E; + public event EventHandler F; + } + public class B : A + { + public new virtual event EventHandler E; + public new event EventHandler F; + } + public class C : B + { + public override event EventHandler E; } } From e5f043e643cf74b8e8b945507248fd54e9b7cf9c Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Fri, 22 Apr 2011 14:22:09 +0200 Subject: [PATCH 09/17] AssemblyList: add Sort() method --- ILSpy/AssemblyList.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/ILSpy/AssemblyList.cs b/ILSpy/AssemblyList.cs index 7229a6503..9585d71f0 100644 --- a/ILSpy/AssemblyList.cs +++ b/ILSpy/AssemblyList.cs @@ -17,7 +17,9 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Collections; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.IO; @@ -143,5 +145,21 @@ namespace ICSharpCode.ILSpy assemblies.Remove(assembly); } } + + public void Sort(IComparer comparer) + { + Sort(0, int.MaxValue, comparer); + } + + public void Sort(int index, int count, IComparer comparer) + { + App.Current.Dispatcher.VerifyAccess(); + lock (assemblies) { + List list = new List(assemblies); + list.Sort(index, Math.Min(count, list.Count - index), comparer); + assemblies.Clear(); + assemblies.AddRange(list); + } + } } } From 600c07388eedda051c8bce02910ffd6ca6ee9c48 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Fri, 22 Apr 2011 19:17:19 +0200 Subject: [PATCH 10/17] Implemented object initializers. --- .../Ast/AstMethodBodyBuilder.cs | 78 ++++-- .../ILAst/ILAstOptimizer.cs | 8 +- ICSharpCode.Decompiler/ILAst/ILCodes.cs | 14 +- ICSharpCode.Decompiler/ILAst/ILInlining.cs | 1 + .../ILAst/InitializerPeepholeTransforms.cs | 222 +++++++++++++++--- ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs | 15 +- .../Tests/InitializerTests.cs | 91 +++++++ 7 files changed, 363 insertions(+), 66 deletions(-) diff --git a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs index 888a9e98d..47bbec344 100644 --- a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs +++ b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs @@ -703,24 +703,72 @@ namespace ICSharpCode.Decompiler.Ast return new Ast.YieldBreakStatement(); case ILCode.YieldReturn: return new Ast.YieldStatement { Expression = arg1 }; - case ILCode.InitCollection: { - ObjectCreateExpression oce = (ObjectCreateExpression)arg1; - oce.Initializer = new ArrayInitializerExpression(); + case ILCode.InitObject: + case ILCode.InitCollection: + { + ArrayInitializerExpression initializer = new ArrayInitializerExpression(); for (int i = 1; i < args.Count; i++) { - ArrayInitializerExpression aie = args[i] as ArrayInitializerExpression; - if (aie != null && aie.Elements.Count == 1) - oce.Initializer.Elements.Add(aie.Elements.Single().Detach()); - else - oce.Initializer.Elements.Add(args[i]); + Match m = objectInitializerPattern.Match(args[i]); + if (m.Success) { + initializer.Elements.Add( + new NamedArgumentExpression { + Identifier = m.Get("left").Single().MemberName, + Expression = m.Get("right").Single().Detach() + }); + } else { + m = collectionInitializerPattern.Match(args[i]); + if (m.Success) { + if (m.Get("arg").Count() == 1) { + initializer.Elements.Add(m.Get("arg").Single().Detach()); + } else { + ArrayInitializerExpression argList = new ArrayInitializerExpression(); + foreach (var expr in m.Get("arg")) { + argList.Elements.Add(expr.Detach()); + } + initializer.Elements.Add(argList); + } + } else { + initializer.Elements.Add(args[i]); + } + } + } + ObjectCreateExpression oce = arg1 as ObjectCreateExpression; + if (oce != null) { + oce.Initializer = initializer; + return oce; + } else { + return new AssignmentExpression(arg1, initializer); } - return oce; - } - case ILCode.InitCollectionAddMethod: { - var collectionInit = new ArrayInitializerExpression(); - collectionInit.Elements.AddRange(args); - return collectionInit; } - default: throw new Exception("Unknown OpCode: " + byteCode.Code); + case ILCode.InitializedObject: + return new InitializedObjectExpression(); + default: + throw new Exception("Unknown OpCode: " + byteCode.Code); + } + } + + static readonly AstNode objectInitializerPattern = new AssignmentExpression( + new MemberReferenceExpression { + Target = new InitializedObjectExpression() + }.WithName("left"), + new AnyNode("right") + ); + + static readonly AstNode collectionInitializerPattern = new InvocationExpression { + Target = new MemberReferenceExpression { + Target = new InitializedObjectExpression(), + MemberName = "Add" + }, + Arguments = { new Repeat(new AnyNode("arg")) } + }; + + sealed class InitializedObjectExpression : IdentifierExpression + { + public InitializedObjectExpression() : base("__initialized_object__") {} + + protected override bool DoMatch(AstNode other, Match match) + { + return other is InitializedObjectExpression; } } diff --git a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs index e069c0499..cf15b9067 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs @@ -27,7 +27,7 @@ namespace ICSharpCode.Decompiler.ILAst TransformDecimalCtorToConstant, SimplifyLdObjAndStObj, TransformArrayInitializers, - TransformCollectionInitializers, + TransformObjectInitializers, MakeAssignmentExpression, IntroducePostIncrement, InlineVariables2, @@ -119,10 +119,10 @@ namespace ICSharpCode.Decompiler.ILAst modified |= block.RunOptimization(SimplifyLdObjAndStObj); if (abortBeforeStep == ILAstOptimizationStep.TransformArrayInitializers) return; - modified |= block.RunOptimization(Initializers.TransformArrayInitializers); + modified |= block.RunOptimization(TransformArrayInitializers); - if (abortBeforeStep == ILAstOptimizationStep.TransformCollectionInitializers) return; - modified |= block.RunOptimization(Initializers.TransformCollectionInitializers); + if (abortBeforeStep == ILAstOptimizationStep.TransformObjectInitializers) return; + modified |= block.RunOptimization(TransformObjectInitializers); if (abortBeforeStep == ILAstOptimizationStep.MakeAssignmentExpression) return; modified |= block.RunOptimization(MakeAssignmentExpression); diff --git a/ICSharpCode.Decompiler/ILAst/ILCodes.cs b/ICSharpCode.Decompiler/ILAst/ILCodes.cs index e26c8d265..e2f91a19e 100644 --- a/ICSharpCode.Decompiler/ILAst/ILCodes.cs +++ b/ICSharpCode.Decompiler/ILAst/ILCodes.cs @@ -238,8 +238,18 @@ namespace ICSharpCode.Decompiler.ILAst LogicOr, NullCoalescing, InitArray, // Array Initializer - InitCollection, // Collection Initializer: first arg is newobj, remaining args are InitCollectionAddMethod method calls - InitCollectionAddMethod, + + // new Class { Prop = 1, Collection = { { 2, 3 }, {4, 5} }} + // is represented as: + // InitObject(newobj Class, + // CallSetter(Prop, InitializedObject, 1), + // InitCollection(CallGetter(Collection, InitializedObject))), + // Call(Add, InitializedObject, 2, 3), + // Call(Add, InitializedObject, 4, 5))) + InitObject, // Object initializer: first arg is newobj, remaining args are the initializing statements + InitCollection, // Collection initializer: first arg is newobj, remaining args are the initializing statements + InitializedObject, // Refers the the object being initialized (refers to first arg in parent InitObject or InitCollection instruction) + TernaryOp, // ?: LoopOrSwitchBreak, LoopContinue, diff --git a/ICSharpCode.Decompiler/ILAst/ILInlining.cs b/ICSharpCode.Decompiler/ILAst/ILInlining.cs index 647f035a7..c1c904951 100644 --- a/ICSharpCode.Decompiler/ILAst/ILInlining.cs +++ b/ICSharpCode.Decompiler/ILAst/ILInlining.cs @@ -197,6 +197,7 @@ namespace ICSharpCode.Decompiler.ILAst { switch (inlinedExpression.Code) { case ILCode.InitArray: + case ILCode.InitObject: case ILCode.InitCollection: case ILCode.DefaultValue: return true; diff --git a/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs b/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs index 29d869526..35853b830 100644 --- a/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs +++ b/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs @@ -3,8 +3,8 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; - using Mono.Cecil; namespace ICSharpCode.Decompiler.ILAst @@ -12,9 +12,10 @@ namespace ICSharpCode.Decompiler.ILAst /// /// IL AST transformation that introduces array, object and collection initializers. /// - public class Initializers + partial class ILAstOptimizer { - public static bool TransformArrayInitializers(List body, ILExpression expr, int pos) + #region Array Initializers + bool TransformArrayInitializers(List body, ILExpression expr, int pos) { ILVariable v, v2, v3; ILExpression newarrExpr; @@ -138,50 +139,201 @@ namespace ICSharpCode.Decompiler.ILAst return false; } } + #endregion - public static bool TransformCollectionInitializers(List body, ILExpression expr, int pos) + /// + /// Handles both object and collection initializers. + /// + bool TransformObjectInitializers(List body, ILExpression expr, int pos) { - ILVariable v, v2; + Debug.Assert(body[pos] == expr); // should be called for top-level expressions only + ILVariable v; ILExpression newObjExpr; MethodReference ctor; List ctorArgs; - if (expr.Match(ILCode.Stloc, out v, out newObjExpr) && - newObjExpr.Match(ILCode.Newobj, out ctor, out ctorArgs)) - { - TypeDefinition td = ctor.DeclaringType.Resolve(); - if (td == null || !td.Interfaces.Any(intf => intf.Name == "IEnumerable" && intf.Namespace == "System.Collections")) + // v = newObj(ctor, ctorArgs) + if (!(expr.Match(ILCode.Stloc, out v, out newObjExpr) && newObjExpr.Match(ILCode.Newobj, out ctor, out ctorArgs))) + return false; + int originalPos = pos; + ILInlining inlining = null; + ILExpression initializer; + if (IsCollectionType(ctor.DeclaringType)) { + // Collection Initializer + initializer = ParseCollectionInitializer(body, ref pos, v, newObjExpr); + if (initializer.Arguments.Count == 1) // only newobj argument, no initializer elements return false; - - // This is a collection: we can convert Add() calls into a collection initializer - ILExpression collectionInitializer = new ILExpression(ILCode.InitCollection, null, newObjExpr); - bool anyAdded = false; - while(pos + 1 < body.Count) { - ILExpression nextExpr = body[pos + 1] as ILExpression; - MethodReference addMethod; - List args; - if (nextExpr.Match(ILCode.Callvirt, out addMethod, out args) && - addMethod.Name == "Add" && - addMethod.HasThis && - args.Count >= 2 && - args[0].Match(ILCode.Ldloc, out v2) && - v == v2) - { - nextExpr.Code = ILCode.InitCollectionAddMethod; - nextExpr.Arguments.RemoveAt(0); - collectionInitializer.Arguments.Add(nextExpr); - body.RemoveAt(pos + 1); - anyAdded = true; + } else { + // Object Initializer + initializer = new ILExpression(ILCode.InitObject, null, newObjExpr); + pos++; + while (pos < body.Count) { + ILExpression nextExpr = body[pos] as ILExpression; + if (IsSetterInObjectInitializer(nextExpr, v)) { + initializer.Arguments.Add(nextExpr); + pos++; } else { - break; + ILVariable collectionVar; + ILExpression getCollection; + if (IsCollectionGetterInObjectInitializer(nextExpr, v, out collectionVar, out getCollection)) { + int posBeforeCollectionInitializer = pos; + ILExpression collectionInitializer = ParseCollectionInitializer(body, ref pos, collectionVar, getCollection); + // Validate that the 'collectionVar' is not read (except in the initializers) + if (inlining == null) { + // instantiate ILInlining only once - we don't change the method while analysing the object initializer + inlining = new ILInlining(method); + } + if (inlining.numLdloc.GetOrDefault(collectionVar) == collectionInitializer.Arguments.Count + && inlining.numStloc.GetOrDefault(collectionVar) == 1 + && inlining.numLdloca.GetOrDefault(collectionVar) == 0) + { + initializer.Arguments.Add(collectionInitializer); + // no need to increment 'pos' here: ParseCollectionInitializer already did that + } else { + // Consider the object initializer to have ended in front of the 'collection getter' instruction. + pos = posBeforeCollectionInitializer; + break; + } + } else { + // can't match any more initializers: end of object initializer + break; + } } } - // ensure we added at least one additional arg to the collection initializer: - if (anyAdded) { - expr.Arguments[0] = collectionInitializer; - return true; + if (initializer.Arguments.Count == 1) + return false; // no initializers were matched (the single argument is the newobjExpr) + } + // Verify that we can inline 'v' into the next instruction: + if (!CanInlineInitializer(body, pos, v, initializer, inlining ?? new ILInlining(method))) + return false; + + expr.Arguments[0] = initializer; + // remove all the instructions that were pulled into the initializer + body.RemoveRange(originalPos + 1, pos - originalPos - 1); + + // now that we know that it's an object initializer, change all the first arguments to 'InitializedObject' + ChangeFirstArgumentToInitializedObject(initializer); + + return true; + } + + /// + /// Gets whether the type supports collection initializers. + /// + static bool IsCollectionType(TypeReference tr) + { + if (tr == null) + return false; + TypeDefinition td = tr.Resolve(); + return td != null && td.Interfaces.Any(intf => intf.Name == "IEnumerable" && intf.Namespace == "System.Collections"); + } + + /// + /// Gets whether 'expr' represents a setter in an object initializer. + /// ('CallvirtSetter(Property, v, value)') + /// + /// The expression to test + /// The variable that contains the object being initialized + static bool IsSetterInObjectInitializer(ILExpression expr, ILVariable v) + { + if (expr == null) + return false; + if (expr.Code == ILCode.CallvirtSetter || expr.Code == ILCode.Stfld) { + if (expr.Arguments.Count == 2) { + return expr.Arguments[0].MatchLdloc(v); } } return false; } + + /// + /// Gets whether 'expr' represents getting a collection in an object initializer. + /// ('collectionVar = callvirtGetter(Property, v)') + /// + /// The expression to test + /// The variable that contains the object being initialized + static bool IsCollectionGetterInObjectInitializer(ILExpression expr, ILVariable v, out ILVariable collectionVar, out ILExpression init) + { + if (expr.Match(ILCode.Stloc, out collectionVar, out init)) { + if (init.Code == ILCode.Ldfld || init.Code == ILCode.CallvirtGetter) { + if (init.Arguments.Count == 1 && init.Arguments[0].MatchLdloc(v)) { + MemberReference mr = (MemberReference)init.Operand; + return IsCollectionType(mr.DeclaringType); + } + } + } + return false; + } + + /// + /// Parses a collection initializer. + /// + /// ILAst block + /// + /// Input: position of the instruction assigning to 'v'. + /// Output: first position after the collection initializer + /// + /// The variable that holds the collection + /// The initial value of the collection (newobj instruction) + /// InitCollection instruction; or null if parsing failed + ILExpression ParseCollectionInitializer(List body, ref int pos, ILVariable v, ILExpression getCollection) + { + Debug.Assert(((ILExpression)body[pos]).Code == ILCode.Stloc); + ILExpression collectionInitializer = new ILExpression(ILCode.InitCollection, null, getCollection); + // Take care not to modify any existing ILExpressions in here. + // We just construct new ones around the old ones, any modifications must wait until the whole + // object/collection initializer was analyzed. + while (++pos < body.Count) { + ILExpression nextExpr = body[pos] as ILExpression; + MethodReference addMethod; + List args; + if (nextExpr.Match(ILCode.Callvirt, out addMethod, out args)) { + if (addMethod.Name == "Add" && addMethod.HasThis) { + if (args.Count >= 2 && args[0].MatchLdloc(v)) { + collectionInitializer.Arguments.Add(nextExpr); + } else { + break; + } + } else { + break; + } + } else { + break; + } + } + return collectionInitializer; + } + + static bool CanInlineInitializer(List body, int pos, ILVariable v, ILExpression initializer, ILInlining inlining) + { + if (pos >= body.Count) + return false; // reached end of block, but there should be another instruction which consumes the initialized object + // one ldloc for each initializer argument except for the newobj, and another ldloc for the use of the initialized object + if (inlining.numLdloc.GetOrDefault(v) != initializer.Arguments.Count) + return false; + if (!(inlining.numStloc.GetOrDefault(v) == 1 && inlining.numLdloca.GetOrDefault(v) == 0)) + return false; + ILExpression nextExpr = body[pos] as ILExpression; + return inlining.CanInlineInto(nextExpr, v, initializer); + } + + static void ChangeFirstArgumentToInitializedObject(ILExpression initializer) + { + // Go through all elements in the initializer (so skip the newobj-instr. at the start) + for (int i = 1; i < initializer.Arguments.Count; i++) { + ILExpression element = initializer.Arguments[i]; + ILExpression arg; + if (element.Code == ILCode.InitCollection) { + // nested collection initializer + ILExpression getCollection = element.Arguments[0]; + arg = getCollection.Arguments[0]; + ChangeFirstArgumentToInitializedObject(element); // handle the collection elements + } else { + arg = element.Arguments[0]; + } + Debug.Assert(arg.Code == ILCode.Ldloc); + arg.Code = ILCode.InitializedObject; + arg.Operand = null; + } + } } } diff --git a/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs b/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs index bf9109a52..78327dc70 100644 --- a/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs +++ b/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs @@ -335,18 +335,13 @@ namespace ICSharpCode.Decompiler.ILAst } return ctor.DeclaringType; } + case ILCode.InitObject: case ILCode.InitCollection: return InferTypeForExpression(expr.Arguments[0], expectedType); - case ILCode.InitCollectionAddMethod: - { - MethodReference addMethod = (MethodReference)expr.Operand; - if (forceInferChildren) { - for (int i = 0; i < addMethod.Parameters.Count; i++) { - InferTypeForExpression(expr.Arguments[i], SubstituteTypeArgs(addMethod.Parameters[i].ParameterType, addMethod)); - } - } - return addMethod.DeclaringType; - } + case ILCode.InitializedObject: + // expectedType should always be known due to the parent method call / property setter + Debug.Assert(expectedType != null); + return expectedType; #endregion #region Load/Store Fields case ILCode.Ldfld: diff --git a/ICSharpCode.Decompiler/Tests/InitializerTests.cs b/ICSharpCode.Decompiler/Tests/InitializerTests.cs index e4c5b06f2..ac2131d1f 100644 --- a/ICSharpCode.Decompiler/Tests/InitializerTests.cs +++ b/ICSharpCode.Decompiler/Tests/InitializerTests.cs @@ -17,6 +17,21 @@ public class InitializerTests c, d } + + class Data + { + public InitializerTests.MyEnum a + { + get; + set; + } + public List PropertyList + { + get; + set; + } + public List FieldList = new List(); + } // Helper methods used to ensure initializers used within expressions work correctly static void X(object a, object b) @@ -140,4 +155,80 @@ public class InitializerTests { InitializerTests.MyEnum.b, InitializerTests.MyEnum2.d } }); } + + public static void NotACollectionInitializer() + { + List list = new List(); + list.Add(1); + list.Add(2); + list.Add(3); + X(Y(), list); + } + + public static void ObjectInitializer() + { + X(Y(), new Data + { + a = InitializerTests.MyEnum.a + }); + } + + public static void NotAObjectInitializer() + { + Data data = new InitializerTests.Data(); + data.a = InitializerTests.MyEnum.a; + X(Y(), data); + } + + public static void ObjectInitializerAssignCollectionToField() + { + X(Y(), new InitializerTests.Data + { + a = InitializerTests.MyEnum.a, + FieldList = new List + { + InitializerTests.MyEnum2.c, + InitializerTests.MyEnum2.d + } + }); + } + + public static void ObjectInitializerAddToCollectionInField() + { + X(Y(), new InitializerTests.Data + { + a = InitializerTests.MyEnum.a, + FieldList = + { + InitializerTests.MyEnum2.c, + InitializerTests.MyEnum2.d + } + }); + } + + public static void ObjectInitializerAssignCollectionToProperty() + { + X(Y(), new InitializerTests.Data + { + a = InitializerTests.MyEnum.a, + PropertyList = new List + { + InitializerTests.MyEnum2.c, + InitializerTests.MyEnum2.d + } + }); + } + + public static void ObjectInitializerAddToCollectionInProperty() + { + X(Y(), new InitializerTests.Data + { + a = InitializerTests.MyEnum.a, + PropertyList = + { + InitializerTests.MyEnum2.c, + InitializerTests.MyEnum2.d + } + }); + } } From baa05b2e01251788c513590112f5c8f1c79e5e0b Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Fri, 22 Apr 2011 19:38:17 +0200 Subject: [PATCH 11/17] Fixed unintended interaction between object initializers and anonymous methods. --- .../Ast/Transforms/DelegateConstruction.cs | 4 +++- .../ILAst/InitializerPeepholeTransforms.cs | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs b/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs index 59c90c28c..871fc2546 100644 --- a/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs @@ -111,6 +111,8 @@ namespace ICSharpCode.Decompiler.Ast.Transforms { if (!context.Settings.AnonymousMethods) return false; // anonymous method decompilation is disabled + if (target != null && !(target is IdentifierExpression || target is ThisReferenceExpression || target is NullReferenceExpression)) + return false; // don't copy arbitrary expressions, deal with identifiers only // Anonymous methods are defined in the same assembly MethodDefinition method = methodRef.ResolveWithinSameModule(); @@ -175,7 +177,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms return true; } - static bool IsPotentialClosure(DecompilerContext context, TypeDefinition potentialDisplayClass) + internal static bool IsPotentialClosure(DecompilerContext context, TypeDefinition potentialDisplayClass) { if (potentialDisplayClass == null || !potentialDisplayClass.IsCompilerGeneratedOrIsInCompilerGeneratedClass()) return false; diff --git a/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs b/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs index 35853b830..f067e47f3 100644 --- a/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs +++ b/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs @@ -164,6 +164,11 @@ namespace ICSharpCode.Decompiler.ILAst return false; } else { // Object Initializer + + // don't use object initializer syntax for closures + if (Ast.Transforms.DelegateConstruction.IsPotentialClosure(context, ctor.DeclaringType.ResolveWithinSameModule())) + return false; + initializer = new ILExpression(ILCode.InitObject, null, newObjExpr); pos++; while (pos < body.Count) { From 207bb984aa0c80da2f58052e3ac5c310b035f89a Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Fri, 22 Apr 2011 22:11:06 +0200 Subject: [PATCH 12/17] Implemented support for nested object/collection initializers. --- .../ILAst/InitializerPeepholeTransforms.cs | 210 ++++++++++-------- ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs | 4 +- .../Tests/InitializerTests.cs | 13 ++ 3 files changed, 134 insertions(+), 93 deletions(-) diff --git a/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs b/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs index f067e47f3..059d4c93c 100644 --- a/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs +++ b/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs @@ -155,13 +155,10 @@ namespace ICSharpCode.Decompiler.ILAst if (!(expr.Match(ILCode.Stloc, out v, out newObjExpr) && newObjExpr.Match(ILCode.Newobj, out ctor, out ctorArgs))) return false; int originalPos = pos; - ILInlining inlining = null; ILExpression initializer; if (IsCollectionType(ctor.DeclaringType)) { // Collection Initializer initializer = ParseCollectionInitializer(body, ref pos, v, newObjExpr); - if (initializer.Arguments.Count == 1) // only newobj argument, no initializer elements - return false; } else { // Object Initializer @@ -169,46 +166,27 @@ namespace ICSharpCode.Decompiler.ILAst if (Ast.Transforms.DelegateConstruction.IsPotentialClosure(context, ctor.DeclaringType.ResolveWithinSameModule())) return false; - initializer = new ILExpression(ILCode.InitObject, null, newObjExpr); - pos++; - while (pos < body.Count) { - ILExpression nextExpr = body[pos] as ILExpression; - if (IsSetterInObjectInitializer(nextExpr, v)) { - initializer.Arguments.Add(nextExpr); - pos++; - } else { - ILVariable collectionVar; - ILExpression getCollection; - if (IsCollectionGetterInObjectInitializer(nextExpr, v, out collectionVar, out getCollection)) { - int posBeforeCollectionInitializer = pos; - ILExpression collectionInitializer = ParseCollectionInitializer(body, ref pos, collectionVar, getCollection); - // Validate that the 'collectionVar' is not read (except in the initializers) - if (inlining == null) { - // instantiate ILInlining only once - we don't change the method while analysing the object initializer - inlining = new ILInlining(method); - } - if (inlining.numLdloc.GetOrDefault(collectionVar) == collectionInitializer.Arguments.Count - && inlining.numStloc.GetOrDefault(collectionVar) == 1 - && inlining.numLdloca.GetOrDefault(collectionVar) == 0) - { - initializer.Arguments.Add(collectionInitializer); - // no need to increment 'pos' here: ParseCollectionInitializer already did that - } else { - // Consider the object initializer to have ended in front of the 'collection getter' instruction. - pos = posBeforeCollectionInitializer; - break; - } - } else { - // can't match any more initializers: end of object initializer - break; - } - } - } - if (initializer.Arguments.Count == 1) - return false; // no initializers were matched (the single argument is the newobjExpr) + initializer = ParseObjectInitializer(body, ref pos, v, newObjExpr); } + + if (initializer.Arguments.Count == 1) // only newobj argument, no initializer elements + return false; + int totalElementCount = pos - originalPos - 1; // totalElementCount: includes elements from nested collections + Debug.Assert(totalElementCount >= initializer.Arguments.Count - 1); + // Verify that we can inline 'v' into the next instruction: - if (!CanInlineInitializer(body, pos, v, initializer, inlining ?? new ILInlining(method))) + + if (pos >= body.Count) + return false; // reached end of block, but there should be another instruction which consumes the initialized object + + ILInlining inlining = new ILInlining(method); + // one ldloc for each initializer argument, and another ldloc for the use of the initialized object + if (inlining.numLdloc.GetOrDefault(v) != totalElementCount + 1) + return false; + if (!(inlining.numStloc.GetOrDefault(v) == 1 && inlining.numLdloca.GetOrDefault(v) == 0)) + return false; + ILExpression nextExpr = body[pos] as ILExpression; + if (!inlining.CanInlineInto(nextExpr, v, initializer)) return false; expr.Arguments[0] = initializer; @@ -236,34 +214,26 @@ namespace ICSharpCode.Decompiler.ILAst /// Gets whether 'expr' represents a setter in an object initializer. /// ('CallvirtSetter(Property, v, value)') /// - /// The expression to test - /// The variable that contains the object being initialized - static bool IsSetterInObjectInitializer(ILExpression expr, ILVariable v) + static bool IsSetterInObjectInitializer(ILExpression expr) { if (expr == null) return false; if (expr.Code == ILCode.CallvirtSetter || expr.Code == ILCode.Stfld) { - if (expr.Arguments.Count == 2) { - return expr.Arguments[0].MatchLdloc(v); - } + return expr.Arguments.Count == 2; } return false; } /// - /// Gets whether 'expr' represents getting a collection in an object initializer. - /// ('collectionVar = callvirtGetter(Property, v)') + /// Gets whether 'expr' represents the invocation of an 'Add' method in a collection initializer. /// - /// The expression to test - /// The variable that contains the object being initialized - static bool IsCollectionGetterInObjectInitializer(ILExpression expr, ILVariable v, out ILVariable collectionVar, out ILExpression init) + static bool IsAddMethodCall(ILExpression expr) { - if (expr.Match(ILCode.Stloc, out collectionVar, out init)) { - if (init.Code == ILCode.Ldfld || init.Code == ILCode.CallvirtGetter) { - if (init.Arguments.Count == 1 && init.Arguments[0].MatchLdloc(v)) { - MemberReference mr = (MemberReference)init.Operand; - return IsCollectionType(mr.DeclaringType); - } + MethodReference addMethod; + List args; + if (expr.Match(ILCode.Callvirt, out addMethod, out args)) { + if (addMethod.Name == "Add" && addMethod.HasThis) { + return args.Count >= 2; } } return false; @@ -278,29 +248,19 @@ namespace ICSharpCode.Decompiler.ILAst /// Output: first position after the collection initializer /// /// The variable that holds the collection - /// The initial value of the collection (newobj instruction) - /// InitCollection instruction; or null if parsing failed - ILExpression ParseCollectionInitializer(List body, ref int pos, ILVariable v, ILExpression getCollection) + /// The initial value of the collection (newobj instruction) + /// InitCollection instruction + ILExpression ParseCollectionInitializer(List body, ref int pos, ILVariable v, ILExpression newObjExpr) { Debug.Assert(((ILExpression)body[pos]).Code == ILCode.Stloc); - ILExpression collectionInitializer = new ILExpression(ILCode.InitCollection, null, getCollection); + ILExpression collectionInitializer = new ILExpression(ILCode.InitCollection, null, newObjExpr); // Take care not to modify any existing ILExpressions in here. // We just construct new ones around the old ones, any modifications must wait until the whole // object/collection initializer was analyzed. while (++pos < body.Count) { ILExpression nextExpr = body[pos] as ILExpression; - MethodReference addMethod; - List args; - if (nextExpr.Match(ILCode.Callvirt, out addMethod, out args)) { - if (addMethod.Name == "Add" && addMethod.HasThis) { - if (args.Count >= 2 && args[0].MatchLdloc(v)) { - collectionInitializer.Arguments.Add(nextExpr); - } else { - break; - } - } else { - break; - } + if (IsAddMethodCall(nextExpr) && nextExpr.Arguments[0].MatchLdloc(v)) { + collectionInitializer.Arguments.Add(nextExpr); } else { break; } @@ -308,17 +268,89 @@ namespace ICSharpCode.Decompiler.ILAst return collectionInitializer; } - static bool CanInlineInitializer(List body, int pos, ILVariable v, ILExpression initializer, ILInlining inlining) + /// + /// Parses an object initializer. + /// + /// ILAst block + /// + /// Input: position of the instruction assigning to 'v'. + /// Output: first position after the object initializer + /// + /// The variable that holds the object being initialized + /// The newobj instruction + /// InitObject instruction + ILExpression ParseObjectInitializer(List body, ref int pos, ILVariable v, ILExpression newObjExpr) { - if (pos >= body.Count) - return false; // reached end of block, but there should be another instruction which consumes the initialized object - // one ldloc for each initializer argument except for the newobj, and another ldloc for the use of the initialized object - if (inlining.numLdloc.GetOrDefault(v) != initializer.Arguments.Count) - return false; - if (!(inlining.numStloc.GetOrDefault(v) == 1 && inlining.numLdloca.GetOrDefault(v) == 0)) + Debug.Assert(((ILExpression)body[pos]).Code == ILCode.Stloc); + // Take care not to modify any existing ILExpressions in here. + // We just construct new ones around the old ones, any modifications must wait until the whole + // object/collection initializer was analyzed. + ILExpression objectInitializer = new ILExpression(ILCode.InitObject, null, newObjExpr); + List initializerStack = new List(); + initializerStack.Add(objectInitializer); + while (++pos < body.Count) { + ILExpression nextExpr = body[pos] as ILExpression; + if (IsSetterInObjectInitializer(nextExpr)) { + if (!AdjustInitializerStack(initializerStack, nextExpr.Arguments[0], v, false)) + break; + initializerStack[initializerStack.Count - 1].Arguments.Add(nextExpr); + } else if (IsAddMethodCall(nextExpr)) { + if (!AdjustInitializerStack(initializerStack, nextExpr.Arguments[0], v, true)) + break; + initializerStack[initializerStack.Count - 1].Arguments.Add(nextExpr); + } else { + // can't match any more initializers: end of object initializer + break; + } + } + return objectInitializer; + } + + static bool AdjustInitializerStack(List initializerStack, ILExpression argument, ILVariable v, bool isCollection) + { + // Argument is of the form 'getter(getter(...(v)))' + // Unpack it into a list of getters: + List getters = new List(); + while (argument.Code == ILCode.CallvirtGetter || argument.Code == ILCode.Ldfld) { + getters.Add(argument); + if (argument.Arguments.Count != 1) + return false; + argument = argument.Arguments[0]; + } + // Ensure that the final argument is 'v' + if (!argument.MatchLdloc(v)) return false; - ILExpression nextExpr = body[pos] as ILExpression; - return inlining.CanInlineInto(nextExpr, v, initializer); + // Now compare the getters with those that are currently active on the initializer stack: + int i; + for (i = 1; i <= Math.Min(getters.Count, initializerStack.Count - 1); i++) { + ILExpression g1 = initializerStack[i].Arguments[0]; // getter stored in initializer + ILExpression g2 = getters[getters.Count - i]; // matching getter from argument + if (g1.Operand != g2.Operand) { + // operands differ, so we abort the comparison + break; + } + } + // Remove all initializers from the stack that were not matched with one from the argument: + initializerStack.RemoveRange(i, initializerStack.Count - i); + // Now create new initializers for the remaining arguments: + for (; i <= getters.Count; i++) { + ILExpression g = getters[getters.Count - i]; + MemberReference mr = (MemberReference)g.Operand; + TypeReference returnType; + if (mr is FieldReference) + returnType = TypeAnalysis.GetFieldType((FieldReference)mr); + else + returnType = TypeAnalysis.SubstituteTypeArgs(((MethodReference)mr).ReturnType, mr); + + ILExpression nestedInitializer = new ILExpression( + IsCollectionType(returnType) ? ILCode.InitCollection : ILCode.InitObject, + null, g); + // add new initializer to its parent, and push it on the stack: + initializerStack[initializerStack.Count - 1].Arguments.Add(nestedInitializer); + initializerStack.Add(nestedInitializer); + } + ILExpression lastInitializer = initializerStack[initializerStack.Count - 1]; + return (lastInitializer.Code == ILCode.InitCollection) == isCollection; } static void ChangeFirstArgumentToInitializedObject(ILExpression initializer) @@ -326,18 +358,14 @@ namespace ICSharpCode.Decompiler.ILAst // Go through all elements in the initializer (so skip the newobj-instr. at the start) for (int i = 1; i < initializer.Arguments.Count; i++) { ILExpression element = initializer.Arguments[i]; - ILExpression arg; - if (element.Code == ILCode.InitCollection) { - // nested collection initializer + if (element.Code == ILCode.InitCollection || element.Code == ILCode.InitObject) { + // nested collection/object initializer ILExpression getCollection = element.Arguments[0]; - arg = getCollection.Arguments[0]; + getCollection.Arguments[0] = new ILExpression(ILCode.InitializedObject, null); ChangeFirstArgumentToInitializedObject(element); // handle the collection elements } else { - arg = element.Arguments[0]; + element.Arguments[0] = new ILExpression(ILCode.InitializedObject, null); } - Debug.Assert(arg.Code == ILCode.Ldloc); - arg.Code = ILCode.InitializedObject; - arg.Operand = null; } } } diff --git a/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs b/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs index 78327dc70..40e853ec6 100644 --- a/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs +++ b/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs @@ -748,12 +748,12 @@ namespace ICSharpCode.Decompiler.ILAst return resultType; } - static TypeReference GetFieldType(FieldReference fieldReference) + public static TypeReference GetFieldType(FieldReference fieldReference) { return SubstituteTypeArgs(UnpackModifiers(fieldReference.FieldType), fieldReference); } - static TypeReference SubstituteTypeArgs(TypeReference type, MemberReference member) + public static TypeReference SubstituteTypeArgs(TypeReference type, MemberReference member) { if (type is TypeSpecification) { ArrayType arrayType = type as ArrayType; diff --git a/ICSharpCode.Decompiler/Tests/InitializerTests.cs b/ICSharpCode.Decompiler/Tests/InitializerTests.cs index ac2131d1f..aba0fd19e 100644 --- a/ICSharpCode.Decompiler/Tests/InitializerTests.cs +++ b/ICSharpCode.Decompiler/Tests/InitializerTests.cs @@ -31,6 +31,8 @@ public class InitializerTests set; } public List FieldList = new List(); + + public InitializerTests.Data MoreData { get; set; } } // Helper methods used to ensure initializers used within expressions work correctly @@ -231,4 +233,15 @@ public class InitializerTests } }); } + + public static void ObjectInitializerWithInitializationOfNestedObjects() + { + X(Y(), new InitializerTests.Data + { + MoreData = + { + a = InitializerTests.MyEnum.a + } + }); + } } From 6a98af56ada1732e3eed698d43c19b961195bc44 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Fri, 22 Apr 2011 22:26:28 +0200 Subject: [PATCH 13/17] Fixed decompilation when an object initializer is used on a collection type. --- .../ILAst/InitializerPeepholeTransforms.cs | 85 +++++++++---------- 1 file changed, 38 insertions(+), 47 deletions(-) diff --git a/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs b/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs index 059d4c93c..9853fed92 100644 --- a/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs +++ b/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs @@ -155,19 +155,12 @@ namespace ICSharpCode.Decompiler.ILAst if (!(expr.Match(ILCode.Stloc, out v, out newObjExpr) && newObjExpr.Match(ILCode.Newobj, out ctor, out ctorArgs))) return false; int originalPos = pos; - ILExpression initializer; - if (IsCollectionType(ctor.DeclaringType)) { - // Collection Initializer - initializer = ParseCollectionInitializer(body, ref pos, v, newObjExpr); - } else { - // Object Initializer - - // don't use object initializer syntax for closures - if (Ast.Transforms.DelegateConstruction.IsPotentialClosure(context, ctor.DeclaringType.ResolveWithinSameModule())) - return false; - - initializer = ParseObjectInitializer(body, ref pos, v, newObjExpr); - } + + // don't use object initializer syntax for closures + if (Ast.Transforms.DelegateConstruction.IsPotentialClosure(context, ctor.DeclaringType.ResolveWithinSameModule())) + return false; + + ILExpression initializer = ParseObjectInitializer(body, ref pos, v, newObjExpr, IsCollectionType(ctor.DeclaringType)); if (initializer.Arguments.Count == 1) // only newobj argument, no initializer elements return false; @@ -196,6 +189,9 @@ namespace ICSharpCode.Decompiler.ILAst // now that we know that it's an object initializer, change all the first arguments to 'InitializedObject' ChangeFirstArgumentToInitializedObject(initializer); + inlining = new ILInlining(method); + inlining.InlineIfPossible(body, ref originalPos); + return true; } @@ -239,35 +235,6 @@ namespace ICSharpCode.Decompiler.ILAst return false; } - /// - /// Parses a collection initializer. - /// - /// ILAst block - /// - /// Input: position of the instruction assigning to 'v'. - /// Output: first position after the collection initializer - /// - /// The variable that holds the collection - /// The initial value of the collection (newobj instruction) - /// InitCollection instruction - ILExpression ParseCollectionInitializer(List body, ref int pos, ILVariable v, ILExpression newObjExpr) - { - Debug.Assert(((ILExpression)body[pos]).Code == ILCode.Stloc); - ILExpression collectionInitializer = new ILExpression(ILCode.InitCollection, null, newObjExpr); - // Take care not to modify any existing ILExpressions in here. - // We just construct new ones around the old ones, any modifications must wait until the whole - // object/collection initializer was analyzed. - while (++pos < body.Count) { - ILExpression nextExpr = body[pos] as ILExpression; - if (IsAddMethodCall(nextExpr) && nextExpr.Arguments[0].MatchLdloc(v)) { - collectionInitializer.Arguments.Add(nextExpr); - } else { - break; - } - } - return collectionInitializer; - } - /// /// Parses an object initializer. /// @@ -279,13 +246,13 @@ namespace ICSharpCode.Decompiler.ILAst /// The variable that holds the object being initialized /// The newobj instruction /// InitObject instruction - ILExpression ParseObjectInitializer(List body, ref int pos, ILVariable v, ILExpression newObjExpr) + ILExpression ParseObjectInitializer(List body, ref int pos, ILVariable v, ILExpression newObjExpr, bool isCollection) { Debug.Assert(((ILExpression)body[pos]).Code == ILCode.Stloc); // Take care not to modify any existing ILExpressions in here. // We just construct new ones around the old ones, any modifications must wait until the whole // object/collection initializer was analyzed. - ILExpression objectInitializer = new ILExpression(ILCode.InitObject, null, newObjExpr); + ILExpression objectInitializer = new ILExpression(isCollection ? ILCode.InitCollection : ILCode.InitObject, null, newObjExpr); List initializerStack = new List(); initializerStack.Add(objectInitializer); while (++pos < body.Count) { @@ -345,12 +312,36 @@ namespace ICSharpCode.Decompiler.ILAst ILExpression nestedInitializer = new ILExpression( IsCollectionType(returnType) ? ILCode.InitCollection : ILCode.InitObject, null, g); - // add new initializer to its parent, and push it on the stack: - initializerStack[initializerStack.Count - 1].Arguments.Add(nestedInitializer); + // add new initializer to its parent: + ILExpression parentInitializer = initializerStack[initializerStack.Count - 1]; + if (parentInitializer.Code == ILCode.InitCollection) { + // can't add children to collection initializer + if (parentInitializer.Arguments.Count == 1) { + // convert empty collection initializer to object initializer + parentInitializer.Code = ILCode.InitObject; + } else { + return false; + } + } + parentInitializer.Arguments.Add(nestedInitializer); initializerStack.Add(nestedInitializer); } ILExpression lastInitializer = initializerStack[initializerStack.Count - 1]; - return (lastInitializer.Code == ILCode.InitCollection) == isCollection; + if (isCollection) { + return lastInitializer.Code == ILCode.InitCollection; + } else { + if (lastInitializer.Code == ILCode.InitCollection) { + if (lastInitializer.Arguments.Count == 1) { + // convert empty collection initializer to object initializer + lastInitializer.Code = ILCode.InitObject; + return true; + } else { + return false; + } + } else { + return true; + } + } } static void ChangeFirstArgumentToInitializedObject(ILExpression initializer) From 7c9013736e00aa6bf737047f42b740932220e64d Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Fri, 22 Apr 2011 22:38:16 +0200 Subject: [PATCH 14/17] Use hyperlinks for the named arguments in object initializers. --- .../Ast/AstMethodBodyBuilder.cs | 5 +++-- ICSharpCode.Decompiler/DecompilerSettings.cs | 15 +++++++++++++++ .../ILAst/InitializerPeepholeTransforms.cs | 3 +++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs index 47bbec344..bf60d2b92 100644 --- a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs +++ b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs @@ -710,11 +710,12 @@ namespace ICSharpCode.Decompiler.Ast for (int i = 1; i < args.Count; i++) { Match m = objectInitializerPattern.Match(args[i]); if (m.Success) { + MemberReferenceExpression mre = m.Get("left").Single(); initializer.Elements.Add( new NamedArgumentExpression { - Identifier = m.Get("left").Single().MemberName, + Identifier = mre.MemberName, Expression = m.Get("right").Single().Detach() - }); + }.CopyAnnotationsFrom(mre)); } else { m = collectionInitializerPattern.Match(args[i]); if (m.Success) { diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index b4fd86485..78ccf7e60 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -179,6 +179,21 @@ namespace ICSharpCode.Decompiler } } + bool objectCollectionInitializers; + + /// + /// Gets/Sets whether to use C# 3.0 object/collection initializers + /// + public bool ObjectOrCollectionInitializers { + get { return objectCollectionInitializers; } + set { + if (objectCollectionInitializers != value) { + objectCollectionInitializers = value; + OnPropertyChanged("ObjectCollectionInitializers"); + } + } + } + public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) diff --git a/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs b/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs index 9853fed92..7c70b5c6f 100644 --- a/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs +++ b/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs @@ -146,6 +146,9 @@ namespace ICSharpCode.Decompiler.ILAst /// bool TransformObjectInitializers(List body, ILExpression expr, int pos) { + if (!context.Settings.ObjectOrCollectionInitializers) + return false; + Debug.Assert(body[pos] == expr); // should be called for top-level expressions only ILVariable v; ILExpression newObjExpr; From 1e13e4a6fae4ac222393a5fb44e89513cace10d4 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Fri, 22 Apr 2011 22:54:30 +0200 Subject: [PATCH 15/17] Show generic constraints in IL view. Closes #140. --- .../Disassembler/DisassemblerHelpers.cs | 5 ++- .../Disassembler/ReflectionDisassembler.cs | 38 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/Disassembler/DisassemblerHelpers.cs b/ICSharpCode.Decompiler/Disassembler/DisassemblerHelpers.cs index 33032d02e..0060b71d8 100644 --- a/ICSharpCode.Decompiler/Disassembler/DisassemblerHelpers.cs +++ b/ICSharpCode.Decompiler/Disassembler/DisassemblerHelpers.cs @@ -126,7 +126,10 @@ namespace ICSharpCode.Decompiler.Disassembler writer.Write(string.Join(", ", at.Dimensions)); writer.Write(']'); } else if (type is GenericParameter) { - writer.WriteReference(type.Name, type); + writer.Write('!'); + if (((GenericParameter)type).Owner.GenericParameterType == GenericParameterType.Method) + writer.Write('!'); + writer.Write(type.Name); } else if (type is ByReferenceType) { ((ByReferenceType)type).ElementType.WriteTo(writer, onlyName, shortName); writer.Write('&'); diff --git a/ICSharpCode.Decompiler/Disassembler/ReflectionDisassembler.cs b/ICSharpCode.Decompiler/Disassembler/ReflectionDisassembler.cs index 3ae34ffb3..5231b78c1 100644 --- a/ICSharpCode.Decompiler/Disassembler/ReflectionDisassembler.cs +++ b/ICSharpCode.Decompiler/Disassembler/ReflectionDisassembler.cs @@ -123,6 +123,7 @@ namespace ICSharpCode.Decompiler.Disassembler method.ReturnType.WriteTo(output); output.Write(' '); output.Write(DisassemblerHelpers.Escape(method.Name)); + WriteTypeParameters(output, method); //( params ) output.Write(" ("); @@ -331,6 +332,7 @@ namespace ICSharpCode.Decompiler.Disassembler WriteFlags(type.Attributes & ~masks, typeAttributes); output.Write(DisassemblerHelpers.Escape(type.Name)); + WriteTypeParameters(output, type); output.MarkFoldStart(defaultCollapsed: isInType); output.WriteLine(); @@ -416,6 +418,42 @@ namespace ICSharpCode.Decompiler.Disassembler CloseBlock("End of class " + type.FullName); isInType = oldIsInType; } + + void WriteTypeParameters(ITextOutput output, IGenericParameterProvider p) + { + if (p.HasGenericParameters) { + output.Write('<'); + for (int i = 0; i < p.GenericParameters.Count; i++) { + if (i > 0) + output.Write(", "); + GenericParameter gp = p.GenericParameters[i]; + if (gp.HasReferenceTypeConstraint) { + output.Write("class "); + } else if (gp.HasNotNullableValueTypeConstraint) { + output.Write("valuetype "); + } + if (gp.HasConstraints) { + output.Write('('); + for (int j = 0; j < gp.Constraints.Count; j++) { + if (j > 0) + output.Write(", "); + gp.Constraints[j].WriteTo(output, true); + } + output.Write(") "); + } + if (gp.HasDefaultConstructorConstraint) { + output.Write(".ctor "); + } + if (gp.IsContravariant) { + output.Write('-'); + } else if (gp.IsCovariant) { + output.Write('+'); + } + output.Write(DisassemblerHelpers.Escape(gp.Name)); + } + output.Write('>'); + } + } #endregion #region Helper methods From 57db57670cb2394ebe450e03cefb88ff7d8d66b5 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Fri, 22 Apr 2011 23:38:38 +0200 Subject: [PATCH 16/17] Fix bug when decompiling lambdas that are nested 3 or more levels (and have a closure on each level). --- .../Ast/Transforms/DelegateConstruction.cs | 22 ++++++++++++++-- .../Tests/DelegateConstruction.cs | 25 +++++++++++++------ 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs b/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs index 871fc2546..1e0ce47bf 100644 --- a/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs @@ -330,12 +330,30 @@ namespace ICSharpCode.Decompiler.Ast.Transforms if (right is ThisReferenceExpression) { isParameter = true; } else if (right is IdentifierExpression) { - // handle parameters only if the whole method contains no other occurrance except for 'right' + // handle parameters only if the whole method contains no other occurrence except for 'right' ILVariable v = right.Annotation(); isParameter = v.IsParameter && parameterOccurrances.Count(c => c == v) == 1; - if (!isParameter && TypeAnalysis.IsSameType(v.Type, fieldDef.FieldType) && IsPotentialClosure(context, v.Type.ResolveWithinSameModule())) { + if (!isParameter && IsPotentialClosure(context, v.Type.ResolveWithinSameModule())) { + // parent display class within the same method + // (closure2.localsX = closure1;) isDisplayClassParentPointerAssignment = true; } + } else if (right is MemberReferenceExpression) { + // copy of parent display class reference from an outer lambda + // closure2.localsX = this.localsY + MemberReferenceExpression mre = m.Get("right").Single(); + do { + // descend into the targets of the mre as long as the field types are closures + FieldDefinition fieldDef2 = mre.Annotation().ResolveWithinSameModule(); + if (fieldDef2 == null || !IsPotentialClosure(context, fieldDef2.FieldType.ResolveWithinSameModule())) { + break; + } + // if we finally get to a this reference, it's copying a display class parent pointer + if (mre.Target is ThisReferenceExpression) { + isDisplayClassParentPointerAssignment = true; + } + mre = mre.Target as MemberReferenceExpression; + } while (mre != null); } if (isParameter || isDisplayClassParentPointerAssignment) { dict[fieldDef] = right; diff --git a/ICSharpCode.Decompiler/Tests/DelegateConstruction.cs b/ICSharpCode.Decompiler/Tests/DelegateConstruction.cs index 248337da2..14f6ce8e1 100644 --- a/ICSharpCode.Decompiler/Tests/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler/Tests/DelegateConstruction.cs @@ -25,21 +25,25 @@ public static class DelegateConstruction public Action CaptureOfThisAndParameterInForEach(int a) { - foreach (var item in Enumerable.Empty()) { - return delegate { - CaptureOfThisAndParameter(item + a); - }; + foreach (int item in Enumerable.Empty()) { + if (item > 0) { + return delegate { + CaptureOfThisAndParameter(item + a); + }; + } } return null; } public Action CaptureOfThisAndParameterInForEachWithItemCopy(int a) { - foreach (var item in Enumerable.Empty()) { + foreach (int item in Enumerable.Empty()) { int copyOfItem = item; - return delegate { - CaptureOfThisAndParameter(item + a + copyOfItem); - }; + if (item > 0) { + return delegate { + CaptureOfThisAndParameter(item + a + copyOfItem); + }; + } } return null; } @@ -162,4 +166,9 @@ public static class DelegateConstruction { return b => c => a + b + c; } + + public static Func>> CurriedAddition2(int a) + { + return b => c => d => a + b + c + d; + } } From 0764fd7ca77e5ca21e080a1f5dced4e5c9f538fd Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Fri, 22 Apr 2011 23:47:28 +0200 Subject: [PATCH 17/17] Fixed issue in lambda decompilation when a closure class contains static fields (cached delegates). --- ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs | 2 ++ ICSharpCode.Decompiler/DecompilerSettings.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs b/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs index 1e0ce47bf..c37957f5f 100644 --- a/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs @@ -369,6 +369,8 @@ namespace ICSharpCode.Decompiler.Ast.Transforms // Now create variables for all fields of the display class (except for those that we already handled as parameters) List> variablesToDeclare = new List>(); foreach (FieldDefinition field in type.Fields) { + if (field.IsStatic) + continue; // skip static fields if (dict.ContainsKey(field)) // skip field if it already was handled as parameter continue; EnsureVariableNameIsAvailable(blockStatement, field.Name); diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index 78ccf7e60..f4a20e047 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -179,7 +179,7 @@ namespace ICSharpCode.Decompiler } } - bool objectCollectionInitializers; + bool objectCollectionInitializers = true; /// /// Gets/Sets whether to use C# 3.0 object/collection initializers