diff --git a/DecompilerNuGetDemos.workbook b/DecompilerNuGetDemos.workbook index 0d4e3d504..6e0d3c3b7 100644 --- a/DecompilerNuGetDemos.workbook +++ b/DecompilerNuGetDemos.workbook @@ -6,7 +6,7 @@ platforms: - DotNetCore packages: - id: ICSharpCode.Decompiler - version: 5.0.0.5124 + version: 6.0.0.5420-preview1 --- Setup: load the references required to work with the decompiler diff --git a/ICSharpCode.Decompiler.Console/ICSharpCode.Decompiler.Console.csproj b/ICSharpCode.Decompiler.Console/ICSharpCode.Decompiler.Console.csproj index 6d337805c..8097b323e 100644 --- a/ICSharpCode.Decompiler.Console/ICSharpCode.Decompiler.Console.csproj +++ b/ICSharpCode.Decompiler.Console/ICSharpCode.Decompiler.Console.csproj @@ -2,21 +2,22 @@ Exe - netcoreapp2.1 + netcoreapp2.1;netcoreapp3.1 + true true true ilspycmd ilspycmd - 5.0.0.5124 + 6.0.0.5420-preview1 Command-line decompiler using the ILSpy decompilation engine Copyright 2011-2019 AlphaSierraPapa https://github.com/icsharpcode/ILSpy/ MIT - ILSpyCmdNuGetPackageIcon.png + ILSpyCmdNuGetPackageIcon.png https://github.com/icsharpcode/ILSpy/ - 5.0.0.0 - 5.0.0.0 + 6.0.0.0 + 6.0.0.0 true ILSpy Team @@ -26,6 +27,12 @@ NU1605 + + + + + + @@ -35,7 +42,7 @@ - + @@ -45,6 +52,7 @@ + diff --git a/ICSharpCode.Decompiler.Console/IlspyCmdProgram.cs b/ICSharpCode.Decompiler.Console/IlspyCmdProgram.cs index 02fdcdf23..3821f042f 100644 --- a/ICSharpCode.Decompiler.Console/IlspyCmdProgram.cs +++ b/ICSharpCode.Decompiler.Console/IlspyCmdProgram.cs @@ -12,6 +12,7 @@ using System.Threading; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; using ICSharpCode.Decompiler.DebugInfo; +using ICSharpCode.Decompiler.PdbProvider; // ReSharper disable All namespace ICSharpCode.Decompiler.Console @@ -45,9 +46,13 @@ Remarks: [Option("-il|--ilcode", "Show IL code.", CommandOptionType.NoValue)] public bool ShowILCodeFlag { get; } - [Option("-d|--debuginfo", "Generate PDB.", CommandOptionType.NoValue)] + [Option("-genpdb", "Generate PDB.", CommandOptionType.NoValue)] public bool CreateDebugInfoFlag { get; } + [FileExistsOrNull] + [Option("-usepdb", "Use PDB.", CommandOptionType.SingleOrNoValue)] + public (bool IsSet, string Value) InputPDBFile { get; } + [Option("-l|--list ", "Lists all entities of the specified type(s). Valid types: c(lass), i(interface), s(truct), d(elegate), e(num)", CommandOptionType.MultipleValue)] public string[] EntityTypes { get; } = new string[0]; @@ -142,7 +147,9 @@ Remarks: foreach (var path in ReferencePaths) { resolver.AddSearchDirectory(path); } - return new CSharpDecompiler(assemblyFileName, resolver, GetSettings()); + return new CSharpDecompiler(assemblyFileName, resolver, GetSettings()) { + DebugInfoProvider = TryLoadPDB(module) + }; } int ListContent(string assemblyFileName, TextWriter output, ISet kinds) @@ -175,6 +182,7 @@ Remarks: resolver.AddSearchDirectory(path); } decompiler.AssemblyResolver = resolver; + decompiler.DebugInfoProvider = TryLoadPDB(module); decompiler.DecompileProject(module, outputDirectory); return 0; } @@ -211,5 +219,16 @@ Remarks: return 0; } + + IDebugInfoProvider TryLoadPDB(PEFile module) + { + if (InputPDBFile.IsSet) { + if (InputPDBFile.Value == null) + return DebugInfoUtils.LoadSymbols(module); + return DebugInfoUtils.FromFile(module, InputPDBFile.Value); + } + + return null; + } } } diff --git a/ICSharpCode.Decompiler.Console/ValidationAttributes.cs b/ICSharpCode.Decompiler.Console/ValidationAttributes.cs index 802382bbf..a593b1674 100644 --- a/ICSharpCode.Decompiler.Console/ValidationAttributes.cs +++ b/ICSharpCode.Decompiler.Console/ValidationAttributes.cs @@ -1,19 +1,38 @@ using System; using System.ComponentModel.DataAnnotations; +using System.IO; namespace ICSharpCode.Decompiler.Console { [AttributeUsage(AttributeTargets.Class)] - public class ProjectOptionRequiresOutputDirectoryValidationAttribute : ValidationAttribute + public sealed class ProjectOptionRequiresOutputDirectoryValidationAttribute : ValidationAttribute { + public ProjectOptionRequiresOutputDirectoryValidationAttribute() + { + } + protected override ValidationResult IsValid(object value, ValidationContext context) { if (value is ILSpyCmdProgram obj) { - if (obj.CreateCompilableProjectFlag && String.IsNullOrEmpty(obj.OutputDirectory)) { + if (obj.CreateCompilableProjectFlag && string.IsNullOrEmpty(obj.OutputDirectory)) { return new ValidationResult("--project cannot be used unless --outputdir is also specified"); } } return ValidationResult.Success; } } + + [AttributeUsage(AttributeTargets.Property)] + public sealed class FileExistsOrNullAttribute : ValidationAttribute + { + protected override ValidationResult IsValid(object value, ValidationContext context) + { + var s = value as string; + if (string.IsNullOrEmpty(s)) + return ValidationResult.Success; + if (File.Exists(s)) + return ValidationResult.Success; + return new ValidationResult($"File '{s}' does not exist!"); + } + } } diff --git a/ICSharpCode.Decompiler.PdbProvider.Cecil/MonoCecilDebugInfoProvider.cs b/ICSharpCode.Decompiler.PdbProvider.Cecil/MonoCecilDebugInfoProvider.cs index 0b8f5a383..9f79d0c73 100644 --- a/ICSharpCode.Decompiler.PdbProvider.Cecil/MonoCecilDebugInfoProvider.cs +++ b/ICSharpCode.Decompiler.PdbProvider.Cecil/MonoCecilDebugInfoProvider.cs @@ -30,7 +30,7 @@ using Mono.Cecil; using Mono.Cecil.Pdb; using SRM = System.Reflection.Metadata; -namespace ICSharpCode.Decompiler.PdbProvider.Cecil +namespace ICSharpCode.Decompiler.PdbProvider { public class MonoCecilDebugInfoProvider : IDebugInfoProvider { diff --git a/ICSharpCode.Decompiler.PowerShell/GetDecompilerCmdlet.cs b/ICSharpCode.Decompiler.PowerShell/GetDecompilerCmdlet.cs index a268c3c42..30616b253 100644 --- a/ICSharpCode.Decompiler.PowerShell/GetDecompilerCmdlet.cs +++ b/ICSharpCode.Decompiler.PowerShell/GetDecompilerCmdlet.cs @@ -1,8 +1,12 @@ using System; using System.Collections.Generic; +using System.IO; using System.Management.Automation; +using System.Reflection.PortableExecutable; using System.Text; using ICSharpCode.Decompiler.CSharp; +using ICSharpCode.Decompiler.Metadata; +using ICSharpCode.Decompiler.PdbProvider; namespace ICSharpCode.Decompiler.PowerShell { @@ -24,18 +28,25 @@ namespace ICSharpCode.Decompiler.PowerShell [Parameter(HelpMessage = "Remove dead code")] public bool RemoveDeadCode { get; set; } + [Parameter(HelpMessage = "Use PDB")] + public string PDBFilePath { get; set; } + protected override void ProcessRecord() { string path = GetUnresolvedProviderPathFromPSPath(LiteralPath); try { + var module = new PEFile(LiteralPath, new FileStream(LiteralPath, FileMode.Open, FileAccess.Read), PEStreamOptions.Default); + var debugInfo = DebugInfoUtils.FromFile(module, PDBFilePath); var decompiler = new CSharpDecompiler(path, new DecompilerSettings(LanguageVersion) { ThrowOnAssemblyResolveErrors = false, RemoveDeadCode = RemoveDeadCode, - RemoveDeadStores = RemoveDeadStores + RemoveDeadStores = RemoveDeadStores, + UseDebugSymbols = debugInfo != null, + ShowDebugInfo = debugInfo != null, }); + decompiler.DebugInfoProvider = debugInfo; WriteObject(decompiler); - } catch (Exception e) { WriteVerbose(e.ToString()); WriteError(new ErrorRecord(e, ErrorIds.AssemblyLoadFailed, ErrorCategory.OperationStopped, null)); diff --git a/ICSharpCode.Decompiler.PowerShell/ICSharpCode.Decompiler.PowerShell.csproj b/ICSharpCode.Decompiler.PowerShell/ICSharpCode.Decompiler.PowerShell.csproj index 4b621af3b..d092cc8f9 100644 --- a/ICSharpCode.Decompiler.PowerShell/ICSharpCode.Decompiler.PowerShell.csproj +++ b/ICSharpCode.Decompiler.PowerShell/ICSharpCode.Decompiler.PowerShell.csproj @@ -2,13 +2,21 @@ netstandard2.0 + true true ICSharpCode.Decompiler.PowerShell - + + + + + + + + diff --git a/ICSharpCode.Decompiler.Tests/Helpers/RemoveCompilerAttribute.cs b/ICSharpCode.Decompiler.Tests/Helpers/RemoveCompilerAttribute.cs index f75ca7a41..d9d89af5e 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/RemoveCompilerAttribute.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/RemoveCompilerAttribute.cs @@ -13,14 +13,12 @@ namespace ICSharpCode.Decompiler.Tests.Helpers var section = (AttributeSection)attribute.Parent; SimpleType type = attribute.Type as SimpleType; if (section.AttributeTarget == "assembly" && - (type.Identifier == "CompilationRelaxations" || type.Identifier == "RuntimeCompatibility" || type.Identifier == "SecurityPermission" || type.Identifier == "PermissionSet" || type.Identifier == "AssemblyVersion" || type.Identifier == "Debuggable" || type.Identifier == "TargetFramework")) - { + (type.Identifier == "CompilationRelaxations" || type.Identifier == "RuntimeCompatibility" || type.Identifier == "SecurityPermission" || type.Identifier == "PermissionSet" || type.Identifier == "AssemblyVersion" || type.Identifier == "Debuggable" || type.Identifier == "TargetFramework")) { attribute.Remove(); if (section.Attributes.Count == 0) section.Remove(); } - if (section.AttributeTarget == "module" && type.Identifier == "UnverifiableCode") - { + if (section.AttributeTarget == "module" && type.Identifier == "UnverifiableCode") { attribute.Remove(); if (section.Attributes.Count == 0) section.Remove(); @@ -60,4 +58,21 @@ namespace ICSharpCode.Decompiler.Tests.Helpers rootNode.AcceptVisitor(this); } } + + public class RemoveNamespaceMy : DepthFirstAstVisitor, IAstTransform + { + public override void VisitNamespaceDeclaration(NamespaceDeclaration namespaceDeclaration) + { + if (namespaceDeclaration.Name == "My") { + namespaceDeclaration.Remove(); + } else { + base.VisitNamespaceDeclaration(namespaceDeclaration); + } + } + + public void Run(AstNode rootNode, TransformContext context) + { + rootNode.AcceptVisitor(this); + } + } } diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs index 8776bac1f..85e2cc4a1 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs @@ -183,7 +183,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers return Regex.Replace(il, @"'\{[0-9A-F-]+\}'", "''"); } - static readonly string coreRefAsmPath = new DotNetCorePathFinder(new Version(3, 0)).GetReferenceAssemblyPath(".NETCoreApp, Version = v3.0"); + static readonly string coreRefAsmPath = new DotNetCorePathFinder(new Version(3, 1)).GetReferenceAssemblyPath(".NETCoreApp, Version = v3.1"); static readonly string refAsmPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), @"Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2"); @@ -209,7 +209,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers const string targetFrameworkAttributeSnippet = @" -[assembly: System.Runtime.Versioning.TargetFramework("".NETCoreApp, Version = v3.0"", FrameworkDisplayName = """")] +[assembly: System.Runtime.Versioning.TargetFramework("".NETCoreApp, Version = v3.1"", FrameworkDisplayName = """")] "; @@ -482,6 +482,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers CSharpDecompiler decompiler = new CSharpDecompiler(typeSystem, settings); decompiler.AstTransforms.Insert(0, new RemoveEmbeddedAttributes()); decompiler.AstTransforms.Insert(0, new RemoveCompilerAttribute()); + decompiler.AstTransforms.Insert(0, new RemoveNamespaceMy()); decompiler.AstTransforms.Add(new EscapeInvalidIdentifiers()); var syntaxTree = decompiler.DecompileWholeModuleAsSingleFile(sortTypes: true); diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index a3070ae32..816c0fb31 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -81,6 +81,7 @@ + @@ -91,6 +92,7 @@ + @@ -261,6 +263,7 @@ + diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.cs index 1acbd5449..5a571f6aa 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.cs @@ -379,11 +379,33 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty return UseSpan(span); } + public string GetSpan3() + { + Span span = stackalloc decimal[GetSize()]; + return UseSpan(span); + } + + public string GetSpan4() + { + Span span = stackalloc decimal[4] { + 1m, + 2m, + 3m, + 4m + }; + return UseSpan(span); + } + public string UseSpan(Span span) { throw new NotImplementedException(); } + public string UseSpan(Span span) + { + throw new NotImplementedException(); + } + public int GetSize() { throw new NotImplementedException(); diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs index 0704d886b..eb8d29d05 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs @@ -1038,7 +1038,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { await Task.Delay(100); if (string.IsNullOrEmpty(str)) { -#if ROSLYN +#if CS70 if (int.TryParse(str, out int id)) { #else int id; @@ -1050,6 +1050,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } } + + public void NullCoalescing() + { + Test>((string a, string b) => a ?? b, (string a, string b) => a ?? b); + Test>((int? a) => a ?? 1, (int? a) => a ?? 1); + } } internal static class Extensions diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PropertiesAndEvents.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PropertiesAndEvents.cs index 5741b77bb..9ad23b9c6 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PropertiesAndEvents.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PropertiesAndEvents.cs @@ -191,6 +191,15 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public event EventHandler AutomaticEventWithInitializer = delegate { }; +#if ROSLYN + // Legacy csc has a bug where EventHandler is only used for the backing field + public event EventHandler DynamicAutoEvent; + public event EventHandler<(int A, string B)> AutoEventWithTuple; +#endif +#if CS80 + public event EventHandler<(int a, dynamic? b)> ComplexAutoEvent; +#endif + public event EventHandler CustomEvent { add { AutomaticEvent += value; diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs index 5125b3982..a6b6813ee 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs @@ -65,6 +65,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } +#if CS73 + public class CustomPinnable + { + public ref int GetPinnableReference() + { + throw new NotImplementedException(); + } + } +#endif + public unsafe delegate void UnsafeDelegate(byte* ptr); private UnsafeDelegate unsafeDelegate; @@ -354,6 +364,22 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty return ptr->ToString(); } +#if CS73 + public unsafe void PinSpan(Span span) + { + fixed (int* ptr = span) { + UsePointer(ptr); + } + } + + //public unsafe void CustomPinReferenceType(CustomPinnable mem) + //{ + // fixed (int* ptr = mem) { + // UsePointer(ptr); + // } + //} +#endif + public unsafe string StackAlloc(int count) { char* ptr = stackalloc char[count]; diff --git a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Select.cs b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Select.cs new file mode 100644 index 000000000..1b9e9ede6 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Select.cs @@ -0,0 +1,37 @@ +using Microsoft.VisualBasic.CompilerServices; +using System; + +[StandardModule] +internal sealed class Program +{ + public static void SelectOnString() + { + switch (Environment.CommandLine) { + case "123": + Console.WriteLine("a"); + break; + case "444": + Console.WriteLine("b"); + break; + case "222": + Console.WriteLine("c"); + break; + case "11": + Console.WriteLine("d"); + break; + case "dd": + Console.WriteLine("e"); + break; + case "sss": + Console.WriteLine("f"); + break; + case "aa": + Console.WriteLine("g"); + break; + case null: + case "": + Console.WriteLine("empty"); + break; + } + } +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Select.vb b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Select.vb new file mode 100644 index 000000000..2fa10c8fd --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Select.vb @@ -0,0 +1,24 @@ +Imports System + +Module Program + Sub SelectOnString() + Select Case Environment.CommandLine + Case "123" + Console.WriteLine("a") + Case "444" + Console.WriteLine("b") + Case "222" + Console.WriteLine("c") + Case "11" + Console.WriteLine("d") + Case "dd" + Console.WriteLine("e") + Case "sss" + Console.WriteLine("f") + Case "aa" + Console.WriteLine("g") + Case "" + Console.WriteLine("empty") + End Select + End Sub +End Module diff --git a/ICSharpCode.Decompiler.Tests/VBPrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/VBPrettyTestRunner.cs index 8cf1c28c0..d41f6aa70 100644 --- a/ICSharpCode.Decompiler.Tests/VBPrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/VBPrettyTestRunner.cs @@ -47,7 +47,7 @@ namespace ICSharpCode.Decompiler.Tests } static readonly CompilerOptions[] defaultOptions = -{ + { CompilerOptions.None, CompilerOptions.Optimize, CompilerOptions.UseRoslyn, @@ -55,7 +55,7 @@ namespace ICSharpCode.Decompiler.Tests }; static readonly CompilerOptions[] roslynOnlyOptions = -{ + { CompilerOptions.UseRoslyn, CompilerOptions.Optimize | CompilerOptions.UseRoslyn, }; @@ -72,6 +72,12 @@ namespace ICSharpCode.Decompiler.Tests Run(options: options | CompilerOptions.Library); } + [Test] + public void Select([ValueSource(nameof(defaultOptions))] CompilerOptions options) + { + Run(options: options | CompilerOptions.Library); + } + void Run([CallerMemberName] string testName = null, CompilerOptions options = CompilerOptions.UseDebug, DecompilerSettings settings = null) { var vbFile = Path.Combine(TestCasePath, testName + ".vb"); diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index 432f706ad..659e5490a 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -819,8 +819,13 @@ namespace ICSharpCode.Decompiler.CSharp var fixedStmt = new FixedStatement(); fixedStmt.Type = exprBuilder.ConvertType(inst.Variable.Type); Expression initExpr; - if (inst.Init.OpCode == OpCode.ArrayToPointer) { - initExpr = exprBuilder.Translate(((ArrayToPointer)inst.Init).Array); + if (inst.Init is GetPinnableReference gpr) { + if (gpr.Method != null) { + IType expectedType = gpr.Method.IsStatic ? gpr.Method.Parameters[0].Type : gpr.Method.DeclaringType; + initExpr = exprBuilder.Translate(gpr.Argument, typeHint: expectedType).ConvertTo(expectedType, exprBuilder); + } else { + initExpr = exprBuilder.Translate(gpr.Argument); + } } else { initExpr = exprBuilder.Translate(inst.Init, typeHint: inst.Variable.Type).ConvertTo(inst.Variable.Type, exprBuilder); } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index ecf39fc27..75b516614 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -1362,6 +1362,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax ClassType classType; switch (typeDefinition.Kind) { case TypeKind.Struct: + case TypeKind.Void: classType = ClassType.Struct; modifiers &= ~Modifiers.Sealed; if (ShowModifiers) { @@ -1420,7 +1421,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax decl.BaseTypes.Add(ConvertType(typeDefinition.EnumUnderlyingType)); } // if the declared type is a struct, ignore System.ValueType - } else if (typeDefinition.Kind == TypeKind.Struct && baseType.IsKnownType(KnownTypeCode.ValueType)) { + } else if ((typeDefinition.Kind == TypeKind.Struct || typeDefinition.Kind == TypeKind.Void) && baseType.IsKnownType(KnownTypeCode.ValueType)) { continue; // always ignore System.Object } else if (baseType.IsKnownType(KnownTypeCode.Object)) { diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs index 41600de3d..4a29f5aac 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs @@ -432,6 +432,17 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms && identExpr.TypeArguments.Count == 0; } + bool CombineDeclarationAndInitializer(VariableToDeclare v, TransformContext context) + { + if (v.Type.IsByRefLike) + return true; // by-ref-like variables always must be initialized at their declaration. + + if (v.InsertionPoint.nextNode.Role == ForStatement.InitializerRole) + return true; // for-statement initializers always should combine declaration and initialization. + + return !context.Settings.SeparateLocalVariableDeclarations; + } + void InsertVariableDeclarations(TransformContext context) { var replacements = new List<(AstNode, AstNode)>(); @@ -439,7 +450,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms if (v.RemovedDueToCollision) continue; - if (IsMatchingAssignment(v, out AssignmentExpression assignment)) { + if (CombineDeclarationAndInitializer(v, context) && IsMatchingAssignment(v, out AssignmentExpression assignment)) { // 'int v; v = expr;' can be combined to 'int v = expr;' AstType type; if (context.Settings.AnonymousTypes && v.Type.ContainsAnonymousType()) { diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs index b4758059d..81814b0b0 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs @@ -165,6 +165,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms public ForStatement TransformFor(ExpressionStatement node) { + if (!context.Settings.ForStatement) + return null; Match m1 = variableAssignPattern.Match(node); if (!m1.Success) return null; var variable = m1.Get("variable").Single().GetILVariable(); @@ -810,16 +812,11 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms default: return false; } - if (!ev.ReturnType.IsMatch(m.Get("type").Single())) { - // Variable types must match event type, - // except that the event type may have an additional nullability annotation - if (ev.ReturnType is ComposedType ct && ct.HasOnlyNullableSpecifier) { - if (!ct.BaseType.IsMatch(m.Get("type").Single())) - return false; - } else { - return false; - } - } + var returnType = ev.ReturnType.GetResolveResult().Type; + var eventType = m.Get("type").Single().GetResolveResult().Type; + // ignore tuple element names, dynamic and nullability + if (!NormalizeTypeVisitor.TypeErasure.EquivalentTypes(returnType, eventType)) + return false; var combineMethod = m.Get("delegateCombine").Single().Parent.GetSymbol() as IMethod; if (combineMethod == null || combineMethod.Name != (isAddAccessor ? "Combine" : "Remove")) return false; @@ -889,9 +886,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms ed.Modifiers = ev.Modifiers; ed.Variables.Add(new VariableInitializer(ev.Name)); ed.CopyAnnotationsFrom(ev); - - IEvent eventDef = ev.GetSymbol() as IEvent; - if (eventDef != null) { + + if (ev.GetSymbol() is IEvent eventDef) { IField field = eventDef.DeclaringType.GetFields(f => f.Name == ev.Name, GetMemberOptions.IgnoreInheritedMembers).SingleOrDefault(); if (field != null) { ed.AddAnnotation(field); @@ -907,7 +903,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } } } - + ev.ReplaceWith(ed); return ed; } @@ -1040,5 +1036,28 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms return base.VisitBinaryOperatorExpression(expr); } #endregion + + #region C# 7.3 pattern based fixed + static readonly Expression addressOfPinnableReference = new UnaryOperatorExpression { + Operator = UnaryOperatorType.AddressOf, + Expression = new InvocationExpression { + Target = new MemberReferenceExpression(new AnyNode("target"), "GetPinnableReference"), + Arguments = { } + } + }; + + public override AstNode VisitFixedStatement(FixedStatement fixedStatement) + { + if (context.Settings.PatternBasedFixedStatement) { + foreach (var v in fixedStatement.Variables) { + var m = addressOfPinnableReference.Match(v.Initializer); + if (m.Success) { + v.Initializer = m.Get("target").Single().Detach(); + } + } + } + return base.VisitFixedStatement(fixedStatement); + } + #endregion } } diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index fed45dec5..2b177db3f 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -104,6 +104,7 @@ namespace ICSharpCode.Decompiler introduceUnmanagedConstraint = false; stackAllocInitializers = false; tupleComparisons = false; + patternBasedFixedStatement = false; } if (languageVersion < CSharp.LanguageVersion.CSharp8_0) { nullableReferenceTypes = false; @@ -117,7 +118,7 @@ namespace ICSharpCode.Decompiler { if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement) return CSharp.LanguageVersion.CSharp8_0; - if (introduceUnmanagedConstraint || tupleComparisons || stackAllocInitializers) + if (introduceUnmanagedConstraint || tupleComparisons || stackAllocInitializers || patternBasedFixedStatement) return CSharp.LanguageVersion.CSharp7_3; if (introduceRefModifiersOnStructs || introduceReadonlyAndInModifiers || nonTrailingNamedArguments || refExtensionMethods) return CSharp.LanguageVersion.CSharp7_2; @@ -926,6 +927,23 @@ namespace ICSharpCode.Decompiler } } + bool patternBasedFixedStatement = true; + + /// + /// Gets/Sets whether C# 7.3 pattern based fixed statement should be used. + /// + [Category("C# 7.3 / VS 2017.7")] + [Description("DecompilerSettings.UsePatternBasedFixedStatement")] + public bool PatternBasedFixedStatement { + get { return patternBasedFixedStatement; } + set { + if (patternBasedFixedStatement != value) { + patternBasedFixedStatement = value; + OnPropertyChanged(); + } + } + } + bool tupleTypes = true; /// @@ -1224,6 +1242,57 @@ namespace ICSharpCode.Decompiler #endregion + bool forStatement = true; + + /// + /// Gets/sets whether the decompiler should produce for loops. + /// + [Category("C# 1.0 / VS .NET")] + [Description("DecompilerSettings.ForStatement")] + public bool ForStatement { + get { return forStatement; } + set { + if (forStatement != value) { + forStatement = value; + OnPropertyChanged(); + } + } + } + + bool doWhileStatement = true; + + /// + /// Gets/sets whether the decompiler should produce do-while loops. + /// + [Category("C# 1.0 / VS .NET")] + [Description("DecompilerSettings.DoWhileStatement")] + public bool DoWhileStatement { + get { return doWhileStatement; } + set { + if (doWhileStatement != value) { + doWhileStatement = value; + OnPropertyChanged(); + } + } + } + + bool separateLocalVariableDeclarations = false; + + /// + /// Gets/sets whether the decompiler should separate local variable declarations from their initialization. + /// + [Category("DecompilerSettings.Other")] + [Description("DecompilerSettings.SeparateLocalVariableDeclarations")] + public bool SeparateLocalVariableDeclarations { + get { return separateLocalVariableDeclarations; } + set { + if (separateLocalVariableDeclarations != value) { + separateLocalVariableDeclarations = value; + OnPropertyChanged(); + } + } + } + CSharpFormattingOptions csharpFormattingOptions; [Browsable(false)] diff --git a/ICSharpCode.Decompiler/Documentation/XmlDocumentationElement.cs b/ICSharpCode.Decompiler/Documentation/XmlDocumentationElement.cs index 7f360b9ed..3b8b0f21e 100644 --- a/ICSharpCode.Decompiler/Documentation/XmlDocumentationElement.cs +++ b/ICSharpCode.Decompiler/Documentation/XmlDocumentationElement.cs @@ -91,8 +91,12 @@ namespace ICSharpCode.Decompiler.Documentation get { if (!referencedEntityInitialized) { string cref = GetAttribute("cref"); - if (!string.IsNullOrEmpty(cref) && crefResolver != null) - referencedEntity = crefResolver(cref); + try { + if (!string.IsNullOrEmpty(cref) && crefResolver != null) + referencedEntity = crefResolver(cref); + } catch { + referencedEntity = null; + } referencedEntityInitialized = true; } return referencedEntity; @@ -113,7 +117,7 @@ namespace ICSharpCode.Decompiler.Documentation /// public string GetAttribute(string name) { - return element?.Attribute(name)?.Value ?? string.Empty; + return element?.Attribute(name)?.Value; } /// diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/DetectPinnedRegions.cs b/ICSharpCode.Decompiler/IL/ControlFlow/DetectPinnedRegions.cs index 049ad5bdc..402ea7f3e 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/DetectPinnedRegions.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/DetectPinnedRegions.cs @@ -139,11 +139,9 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow bool modified = false; for (int i = 0; i < container.Blocks.Count; i++) { var block = container.Blocks[i]; - ILVariable v, p; - Block targetBlock; - if (IsNullSafeArrayToPointerPattern(block, out v, out p, out targetBlock)) { + if (IsNullSafeArrayToPointerPattern(block, out ILVariable v, out ILVariable p, out Block targetBlock)) { context.Step("NullSafeArrayToPointerPattern", block); - ILInstruction arrayToPointer = new ArrayToPointer(new LdLoc(v)); + ILInstruction arrayToPointer = new GetPinnableReference(new LdLoc(v), null); if (p.StackType != StackType.Ref) { arrayToPointer = new Conv(arrayToPointer, p.StackType.ToPrimitiveType(), false, Sign.None); } @@ -432,7 +430,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow return; // variable access that is not LdLoc } } - if (!(ldloc.Parent is ArrayToPointer arrayToPointer)) + if (!(ldloc.Parent is GetPinnableReference arrayToPointer)) return; if (!(arrayToPointer.Parent is Conv conv && conv.Kind == ConversionKind.StopGCTracking)) return; @@ -446,7 +444,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow newVar.HasGeneratedName = oldVar.HasGeneratedName; oldVar.Function.Variables.Add(newVar); pinnedRegion.Variable = newVar; - pinnedRegion.Init = new ArrayToPointer(pinnedRegion.Init).WithILRange(arrayToPointer); + pinnedRegion.Init = new GetPinnableReference(pinnedRegion.Init, arrayToPointer.Method).WithILRange(arrayToPointer); conv.ReplaceWith(new LdLoc(newVar).WithILRange(conv)); } @@ -514,7 +512,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow newVar.HasGeneratedName = pinnedRegion.Variable.HasGeneratedName; pinnedRegion.Variable.Function.Variables.Add(newVar); pinnedRegion.Variable = newVar; - pinnedRegion.Init = new ArrayToPointer(pinnedRegion.Init); + pinnedRegion.Init = new GetPinnableReference(pinnedRegion.Init, null); } return; } @@ -542,7 +540,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow // make targetBlock the new entry point body.Blocks.RemoveAt(targetBlock.ChildIndex); body.Blocks.Insert(0, targetBlock); - pinnedRegion.Init = new ArrayToPointer(pinnedRegion.Init); + pinnedRegion.Init = new GetPinnableReference(pinnedRegion.Init, null); ILVariable otherVar; ILInstruction otherVarInit; diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs index 5156a9fef..0c377228e 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs @@ -131,7 +131,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow AnalyzeCurrentProperty(); ResolveIEnumerableIEnumeratorFieldMapping(); ConstructExceptionTable(); - newBody = AnalyzeMoveNext(); + newBody = AnalyzeMoveNext(function); } catch (SymbolicAnalysisFailedException) { return; } @@ -525,12 +525,14 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow #endregion #region Analyze MoveNext() and generate new body - BlockContainer AnalyzeMoveNext() + BlockContainer AnalyzeMoveNext(ILFunction function) { context.StepStartGroup("AnalyzeMoveNext"); MethodDefinitionHandle moveNextMethod = metadata.GetTypeDefinition(enumeratorType).GetMethods().FirstOrDefault(m => metadata.GetString(metadata.GetMethodDefinition(m).Name) == "MoveNext"); ILFunction moveNextFunction = CreateILAst(moveNextMethod, context); + function.MoveNextMethod = moveNextFunction.Method; + // Copy-propagate temporaries holding a copy of 'this'. // This is necessary because the old (pre-Roslyn) C# compiler likes to store 'this' in temporary variables. foreach (var stloc in moveNextFunction.Descendants.OfType().Where(s => s.Variable.IsSingleDefinition && s.Value.MatchLdThis()).ToList()) { diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs index 0d01840b5..6a3192338 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.cs +++ b/ICSharpCode.Decompiler/IL/Instructions.cs @@ -184,9 +184,12 @@ namespace ICSharpCode.Decompiler.IL LdLen, /// Load address of array element. LdElema, - /// Converts an array pointer (O) to a reference to the first element, or to a null reference if the array is null or empty. - /// Also used to convert a string to a reference to the first character. - ArrayToPointer, + /// Retrieves a pinnable reference for the input object. + /// The input must be an object reference (O). + /// If the input is an array/string, evaluates to a reference to the first element/character, or to a null reference if the array is null or empty. + /// Otherwise, uses the GetPinnableReference method to get the reference, or evaluates to a null reference if the input is null. + /// + GetPinnableReference, /// Maps a string value to an integer. This is used in switch(string). StringToInt, /// ILAst representation of Expression.Convert. @@ -4756,21 +4759,25 @@ namespace ICSharpCode.Decompiler.IL } namespace ICSharpCode.Decompiler.IL { - /// Converts an array pointer (O) to a reference to the first element, or to a null reference if the array is null or empty. - /// Also used to convert a string to a reference to the first character. - public sealed partial class ArrayToPointer : ILInstruction + /// Retrieves a pinnable reference for the input object. + /// The input must be an object reference (O). + /// If the input is an array/string, evaluates to a reference to the first element/character, or to a null reference if the array is null or empty. + /// Otherwise, uses the GetPinnableReference method to get the reference, or evaluates to a null reference if the input is null. + /// + public sealed partial class GetPinnableReference : ILInstruction, IInstructionWithMethodOperand { - public ArrayToPointer(ILInstruction array) : base(OpCode.ArrayToPointer) + public GetPinnableReference(ILInstruction argument, IMethod method) : base(OpCode.GetPinnableReference) { - this.Array = array; + this.Argument = argument; + this.method = method; } - public static readonly SlotInfo ArraySlot = new SlotInfo("Array", canInlineInto: true); - ILInstruction array; - public ILInstruction Array { - get { return this.array; } + public static readonly SlotInfo ArgumentSlot = new SlotInfo("Argument", canInlineInto: true); + ILInstruction argument; + public ILInstruction Argument { + get { return this.argument; } set { ValidateChild(value); - SetChildInstruction(ref this.array, value, 0); + SetChildInstruction(ref this.argument, value, 0); } } protected sealed override int GetChildCount() @@ -4781,7 +4788,7 @@ namespace ICSharpCode.Decompiler.IL { switch (index) { case 0: - return this.array; + return this.argument; default: throw new IndexOutOfRangeException(); } @@ -4790,7 +4797,7 @@ namespace ICSharpCode.Decompiler.IL { switch (index) { case 0: - this.Array = value; + this.Argument = value; break; default: throw new IndexOutOfRangeException(); @@ -4800,21 +4807,24 @@ namespace ICSharpCode.Decompiler.IL { switch (index) { case 0: - return ArraySlot; + return ArgumentSlot; default: throw new IndexOutOfRangeException(); } } public sealed override ILInstruction Clone() { - var clone = (ArrayToPointer)ShallowClone(); - clone.Array = this.array.Clone(); + var clone = (GetPinnableReference)ShallowClone(); + clone.Argument = this.argument.Clone(); return clone; } public override StackType ResultType { get { return StackType.Ref; } } + readonly IMethod method; + /// Returns the method operand. + public IMethod Method { get { return method; } } protected override InstructionFlags ComputeFlags() { - return array.Flags; + return argument.Flags; } public override InstructionFlags DirectFlags { get { @@ -4825,31 +4835,33 @@ namespace ICSharpCode.Decompiler.IL { WriteILRange(output, options); output.Write(OpCode); + output.Write(' '); + method.WriteTo(output); output.Write('('); - this.array.WriteTo(output, options); + this.argument.WriteTo(output, options); output.Write(')'); } public override void AcceptVisitor(ILVisitor visitor) { - visitor.VisitArrayToPointer(this); + visitor.VisitGetPinnableReference(this); } public override T AcceptVisitor(ILVisitor visitor) { - return visitor.VisitArrayToPointer(this); + return visitor.VisitGetPinnableReference(this); } public override T AcceptVisitor(ILVisitor visitor, C context) { - return visitor.VisitArrayToPointer(this, context); + return visitor.VisitGetPinnableReference(this, context); } protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match) { - var o = other as ArrayToPointer; - return o != null && this.array.PerformMatch(o.array, ref match); + var o = other as GetPinnableReference; + return o != null && this.argument.PerformMatch(o.argument, ref match) && method.Equals(o.method); } internal override void CheckInvariant(ILPhase phase) { base.CheckInvariant(phase); - Debug.Assert(array.ResultType == StackType.O); + Debug.Assert(argument.ResultType == StackType.O); } } } @@ -6717,7 +6729,7 @@ namespace ICSharpCode.Decompiler.IL { Default(inst); } - protected internal virtual void VisitArrayToPointer(ArrayToPointer inst) + protected internal virtual void VisitGetPinnableReference(GetPinnableReference inst) { Default(inst); } @@ -7103,7 +7115,7 @@ namespace ICSharpCode.Decompiler.IL { return Default(inst); } - protected internal virtual T VisitArrayToPointer(ArrayToPointer inst) + protected internal virtual T VisitGetPinnableReference(GetPinnableReference inst) { return Default(inst); } @@ -7489,7 +7501,7 @@ namespace ICSharpCode.Decompiler.IL { return Default(inst, context); } - protected internal virtual T VisitArrayToPointer(ArrayToPointer inst, C context) + protected internal virtual T VisitGetPinnableReference(GetPinnableReference inst, C context) { return Default(inst, context); } @@ -7651,7 +7663,7 @@ namespace ICSharpCode.Decompiler.IL "sizeof", "ldlen", "ldelema", - "array.to.pointer", + "get.pinnable.reference", "string.to.int", "expression.tree.cast", "user.logic.operator", @@ -8202,14 +8214,16 @@ namespace ICSharpCode.Decompiler.IL array = default(ILInstruction); return false; } - public bool MatchArrayToPointer(out ILInstruction array) + public bool MatchGetPinnableReference(out ILInstruction argument, out IMethod method) { - var inst = this as ArrayToPointer; + var inst = this as GetPinnableReference; if (inst != null) { - array = inst.Array; + argument = inst.Argument; + method = inst.Method; return true; } - array = default(ILInstruction); + argument = default(ILInstruction); + method = default(IMethod); return false; } public bool MatchUserDefinedLogicOperator(out IMethod method, out ILInstruction left, out ILInstruction right) diff --git a/ICSharpCode.Decompiler/IL/Instructions.tt b/ICSharpCode.Decompiler/IL/Instructions.tt index db99589f3..c4889bb7f 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.tt +++ b/ICSharpCode.Decompiler/IL/Instructions.tt @@ -279,9 +279,11 @@ new OpCode("ldelema", "Load address of array element.", CustomClassName("LdElema"), HasTypeOperand, CustomChildren(new [] { new ArgumentInfo("array"), new ArgumentInfo("indices") { IsCollection = true } }, true), MayThrowIfNotDelayed, ResultType("Ref"), SupportsReadonlyPrefix), - new OpCode("array.to.pointer", "Converts an array pointer (O) to a reference to the first element, or to a null reference if the array is null or empty." + Environment.NewLine - + "Also used to convert a string to a reference to the first character.", - CustomArguments(("array", new[] { "O" })), ResultType("Ref")), + new OpCode("get.pinnable.reference", "Retrieves a pinnable reference for the input object." + Environment.NewLine + + "The input must be an object reference (O)." + Environment.NewLine + + "If the input is an array/string, evaluates to a reference to the first element/character, or to a null reference if the array is null or empty." + Environment.NewLine + + "Otherwise, uses the GetPinnableReference method to get the reference, or evaluates to a null reference if the input is null." + Environment.NewLine, + CustomArguments(("argument", new[] { "O" })), ResultType("Ref"), HasMethodOperand), new OpCode("string.to.int", "Maps a string value to an integer. This is used in switch(string).", CustomArguments(("argument", new[] { "O" })), CustomConstructor, CustomWriteTo, ResultType("I4")), diff --git a/ICSharpCode.Decompiler/IL/PointerArithmeticOffset.cs b/ICSharpCode.Decompiler/IL/PointerArithmeticOffset.cs index d8048b2ed..4a32b9358 100644 --- a/ICSharpCode.Decompiler/IL/PointerArithmeticOffset.cs +++ b/ICSharpCode.Decompiler/IL/PointerArithmeticOffset.cs @@ -77,6 +77,8 @@ namespace ICSharpCode.Decompiler.IL case KnownTypeCode.UInt64: case KnownTypeCode.Double: return 8; + case KnownTypeCode.Decimal: + return 16; } return null; } diff --git a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs index 80007b1c7..61913889f 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs @@ -140,7 +140,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms // remove unused variables before assigning names function.Variables.RemoveDead(); int numDisplayClassLocals = 0; - foreach (var v in function.Variables) { + foreach (var v in function.Variables.OrderBy(v => v.Name)) { switch (v.Kind) { case VariableKind.Parameter: // ignore break; @@ -243,8 +243,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms reservedVariableNames.Add(nameWithoutDigits, number - 1); } int count = ++reservedVariableNames[nameWithoutDigits]; + string nameWithDigits = nameWithoutDigits + count.ToString(); + if (oldVariableName == nameWithDigits) { + return oldVariableName; + } if (count != 1) { - return nameWithoutDigits + count.ToString(); + return nameWithDigits; } else { return nameWithoutDigits; } diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs index 0d73b2586..061ee7721 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs @@ -405,7 +405,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms { var pointerType = new PointerType(elementType); var elementCountInstr = PointerArithmeticOffset.Detect(sizeInBytesInstr, pointerType.ElementType, checkForOverflow: true, unwrapZeroExtension: true); - if (!elementCountInstr.Match(elementCountInstr2).Success) + if (elementCountInstr == null || !elementCountInstr.Match(elementCountInstr2).Success) return false; return true; } diff --git a/ICSharpCode.Decompiler/IL/Transforms/HighLevelLoopTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/HighLevelLoopTransform.cs index 2e4df8080..b119f0c62 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/HighLevelLoopTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/HighLevelLoopTransform.cs @@ -42,10 +42,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (loop.Kind != ContainerKind.Loop) continue; if (MatchWhileLoop(loop, out var condition, out var loopBody)) { - MatchForLoop(loop, condition, loopBody); + if (context.Settings.ForStatement) + MatchForLoop(loop, condition, loopBody); continue; } - if (MatchDoWhileLoop(loop)) + if (context.Settings.DoWhileStatement && MatchDoWhileLoop(loop)) continue; } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs index 1983006f2..95fd133ba 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs @@ -459,7 +459,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms break; case OpCode.DynamicCompoundAssign: return true; - case OpCode.ArrayToPointer: + case OpCode.GetPinnableReference: case OpCode.LocAllocSpan: return true; // inline size-expressions into localloc.span case OpCode.Call: diff --git a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs index 46d6a304c..7b39f389c 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs @@ -160,6 +160,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms // if (comp(ldloc switchValueVar == ldnull)) br defaultBlock if (!instructions[i].MatchIfInstruction(out var condition, out var firstBlockOrDefaultJump)) return false; + var nextCaseJump = instructions[i + 1]; + while (condition.MatchLogicNot(out var arg)) { + condition = arg; + ExtensionMethods.Swap(ref firstBlockOrDefaultJump, ref nextCaseJump); + } + // match call to operator ==(string, string) + if (!MatchStringEqualityComparison(condition, out var switchValueVar, out string firstBlockValue, out bool isVBCompareString)) + return false; + if (isVBCompareString) { + ExtensionMethods.Swap(ref firstBlockOrDefaultJump, ref nextCaseJump); + } if (firstBlockOrDefaultJump.MatchBranch(out var firstBlock)) { // success @@ -172,11 +183,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms List<(string, ILInstruction)> values = new List<(string, ILInstruction)>(); ILInstruction switchValue = null; - - // match call to operator ==(string, string) - if (!MatchStringEqualityComparison(condition, out var switchValueVar, out string firstBlockValue)) - return false; - values.Add((firstBlockValue, firstBlock ?? firstBlockOrDefaultJump)); + if (isVBCompareString && string.IsNullOrEmpty(firstBlockValue)) { + values.Add((null, firstBlock ?? firstBlockOrDefaultJump)); + values.Add((string.Empty, firstBlock ?? firstBlockOrDefaultJump)); + } else { + values.Add((firstBlockValue, firstBlock ?? firstBlockOrDefaultJump)); + } bool extraLoad = false; bool keepAssignmentBefore = false; @@ -215,13 +227,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms switchValue = new LdLoc(switchValueVar); } // if instruction must be followed by a branch to the next case - if (!(instructions.ElementAtOrDefault(i + 1) is Branch nextCaseJump)) + if (!nextCaseJump.MatchBranch(out Block currentCaseBlock)) return false; // extract all cases and add them to the values list. - Block currentCaseBlock = nextCaseJump.TargetBlock; ILInstruction nextCaseBlock; - while ((nextCaseBlock = MatchCaseBlock(currentCaseBlock, switchValueVar, out string value, out Block block)) != null) { - values.Add((value, block)); + while ((nextCaseBlock = MatchCaseBlock(currentCaseBlock, switchValueVar, out string value, out bool emptyStringEqualsNull, out Block block)) != null) { + if (emptyStringEqualsNull && string.IsNullOrEmpty(value)) { + values.Add((null, block)); + values.Add((string.Empty, block)); + } else { + values.Add((value, block)); + } currentCaseBlock = nextCaseBlock as Block; if (currentCaseBlock == null) break; @@ -367,37 +383,34 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// The is updated if the value gets copied to a different variable. /// See comments below for more info. /// - ILInstruction MatchCaseBlock(Block currentBlock, ILVariable switchVariable, out string value, out Block caseBlock) + ILInstruction MatchCaseBlock(Block currentBlock, ILVariable switchVariable, out string value, out bool emptyStringEqualsNull, out Block caseBlock) { value = null; caseBlock = null; + emptyStringEqualsNull = false; if (currentBlock.IncomingEdgeCount != 1 || currentBlock.Instructions.Count != 2) return null; - if (!currentBlock.Instructions[0].MatchIfInstruction(out var condition, out var caseBlockBranch)) + if (!currentBlock.MatchIfAtEndOfBlock(out var condition, out var caseBlockBranch, out var nextBlockBranch)) + return null; + if (!MatchStringEqualityComparison(condition, switchVariable, out value, out bool isVBCompareString)) { return null; + } + if (isVBCompareString) { + ExtensionMethods.Swap(ref caseBlockBranch, ref nextBlockBranch); + emptyStringEqualsNull = true; + } if (!caseBlockBranch.MatchBranch(out caseBlock)) return null; - Block nextBlock; - BlockContainer blockContainer = null; - if (condition.MatchLogicNot(out var inner)) { - condition = inner; - nextBlock = caseBlock; - if (!currentBlock.Instructions[1].MatchBranch(out caseBlock)) - return null; + if (nextBlockBranch.MatchBranch(out Block nextBlock)) { + // success + return nextBlock; + } else if (nextBlockBranch.MatchLeave(out BlockContainer blockContainer)) { + // success + return blockContainer; } else { - if (currentBlock.Instructions[1].MatchBranch(out nextBlock)) { - // success - } else if (currentBlock.Instructions[1].MatchLeave(out blockContainer)) { - // success - } else { - return null; - } - } - if (!MatchStringEqualityComparison(condition, switchVariable, out value)) { return null; } - return nextBlock ?? (ILInstruction)blockContainer; } /// @@ -836,8 +849,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms MatchComputeStringHashCall(switchBlockInstructions[switchBlockInstructionsOffset], switchValueVar, out LdLoc switchValueLoad))) return false; - var stringValues = new List<(int Index, string Value, ILInstruction TargetBlockOrLeave)>(); - int index = 0; + var stringValues = new List<(string Value, ILInstruction TargetBlockOrLeave)>(); SwitchSection defaultSection = switchInst.Sections.MaxBy(s => s.Labels.Count()); Block exitOrDefaultBlock = null; foreach (var section in switchInst.Sections) { @@ -846,20 +858,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!section.Body.MatchBranch(out Block target)) return false; string stringValue; + bool emptyStringEqualsNull; if (MatchRoslynEmptyStringCaseBlockHead(target, switchValueLoad.Variable, out ILInstruction targetOrLeave, out Block currentExitBlock)) { stringValue = ""; - } else if (!MatchRoslynCaseBlockHead(target, switchValueLoad.Variable, out targetOrLeave, out currentExitBlock, out stringValue)) { + emptyStringEqualsNull = false; + } else if (!MatchRoslynCaseBlockHead(target, switchValueLoad.Variable, out targetOrLeave, out currentExitBlock, out stringValue, out emptyStringEqualsNull)) { return false; } if (exitOrDefaultBlock != null && exitOrDefaultBlock != currentExitBlock) return false; exitOrDefaultBlock = currentExitBlock; - stringValues.Add((index++, stringValue, targetOrLeave)); + if (emptyStringEqualsNull && string.IsNullOrEmpty(stringValue)) { + stringValues.Add((null, targetOrLeave)); + stringValues.Add((string.Empty, targetOrLeave)); + } else { + stringValues.Add((stringValue, targetOrLeave)); + } } if (nullValueCaseBlock != null && exitOrDefaultBlock != nullValueCaseBlock) { - stringValues.Add((index++, null, nullValueCaseBlock)); + stringValues.Add((null, nullValueCaseBlock)); } ILInstruction switchValueInst = switchValueLoad; @@ -914,13 +933,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms SwitchInstruction ReplaceWithSwitchInstruction(int offset) { - var defaultLabel = new LongSet(new LongInterval(0, index)).Invert(); + var defaultLabel = new LongSet(new LongInterval(0, stringValues.Count)).Invert(); var values = new string[stringValues.Count]; var sections = new SwitchSection[stringValues.Count]; - foreach (var (idx, (label, value, bodyInstruction)) in stringValues.WithIndex()) { + foreach (var (idx, (value, bodyInstruction)) in stringValues.WithIndex()) { values[idx] = value; var body = bodyInstruction is Block b ? new Branch(b) : bodyInstruction; - sections[idx] = new SwitchSection { Labels = new LongSet(label), Body = body }; + sections[idx] = new SwitchSection { Labels = new LongSet(idx), Body = body }; } var newSwitch = new SwitchInstruction(new StringToInt(switchValueInst, values)); newSwitch.Sections.AddRange(sections); @@ -935,26 +954,28 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// if (call op_Equality(ldloc switchValueVar, stringValue)) br body /// br exit /// - bool MatchRoslynCaseBlockHead(Block target, ILVariable switchValueVar, out ILInstruction bodyOrLeave, out Block defaultOrExitBlock, out string stringValue) + bool MatchRoslynCaseBlockHead(Block target, ILVariable switchValueVar, out ILInstruction bodyOrLeave, out Block defaultOrExitBlock, out string stringValue, out bool emptyStringEqualsNull) { bodyOrLeave = null; defaultOrExitBlock = null; stringValue = null; + emptyStringEqualsNull = false; if (target.Instructions.Count != 2) return false; if (!target.Instructions[0].MatchIfInstruction(out var condition, out var bodyBranch)) return false; - ILInstruction exitBranch; + ILInstruction exitBranch = target.Instructions[1]; // Handle negated conditions first: - if (condition.MatchLogicNot(out var expr)) { - exitBranch = bodyBranch; - bodyBranch = target.Instructions[1]; + while (condition.MatchLogicNot(out var expr)) { + ExtensionMethods.Swap(ref exitBranch, ref bodyBranch); condition = expr; - } else { - exitBranch = target.Instructions[1]; } - if (!MatchStringEqualityComparison(condition, switchValueVar, out stringValue)) + if (!MatchStringEqualityComparison(condition, switchValueVar, out stringValue, out bool isVBCompareString)) return false; + if (isVBCompareString) { + ExtensionMethods.Swap(ref exitBranch, ref bodyBranch); + emptyStringEqualsNull = true; + } if (!(exitBranch.MatchBranch(out defaultOrExitBlock) || exitBranch.MatchLeave(out _))) return false; if (bodyBranch.MatchLeave(out _)) { @@ -1054,25 +1075,45 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// Matches 'call string.op_Equality(ldloc(variable), ldstr(stringValue))' /// or 'comp(ldloc(variable) == ldnull)' /// - bool MatchStringEqualityComparison(ILInstruction condition, ILVariable variable, out string stringValue) + bool MatchStringEqualityComparison(ILInstruction condition, ILVariable variable, out string stringValue, out bool isVBCompareString) { - return MatchStringEqualityComparison(condition, out var v, out stringValue) && v == variable; + return MatchStringEqualityComparison(condition, out var v, out stringValue, out isVBCompareString) && v == variable; } /// /// Matches 'call string.op_Equality(ldloc(variable), ldstr(stringValue))' /// or 'comp(ldloc(variable) == ldnull)' /// - bool MatchStringEqualityComparison(ILInstruction condition, out ILVariable variable, out string stringValue) + bool MatchStringEqualityComparison(ILInstruction condition, out ILVariable variable, out string stringValue, out bool isVBCompareString) { stringValue = null; variable = null; - ILInstruction left, right; - if (condition is Call c && c.Method.IsOperator && c.Method.Name == "op_Equality" - && c.Method.DeclaringType.IsKnownType(KnownTypeCode.String) && c.Arguments.Count == 2) - { - left = c.Arguments[0]; - right = c.Arguments[1]; + isVBCompareString = false; + while (condition is Comp comp && comp.Kind == ComparisonKind.Inequality && comp.Right.MatchLdcI4(0)) { + // if (x != 0) == if (x) + condition = comp.Left; + } + if (condition is Call c) { + ILInstruction left, right; + if (c.Method.IsOperator && c.Method.Name == "op_Equality" + && c.Method.DeclaringType.IsKnownType(KnownTypeCode.String) && c.Arguments.Count == 2) { + left = c.Arguments[0]; + right = c.Arguments[1]; + } else if (c.Method.IsStatic && c.Method.Name == "CompareString" + && c.Method.DeclaringType.FullName == "Microsoft.VisualBasic.CompilerServices.Operators" + && c.Arguments.Count == 3) { + left = c.Arguments[0]; + right = c.Arguments[1]; + // VB CompareString(): return 0 on equality -> condition is effectively negated. + // Also, the empty string is considered equal to null. + isVBCompareString = true; + if (!c.Arguments[2].MatchLdcI4(0)) { + // Option Compare Text: case insensitive comparison is not supported in C# + return false; + } + } else { + return false; + } return left.MatchLdLoc(out variable) && right.MatchLdStr(out stringValue); } else if (condition.MatchCompEqualsNull(out var arg)) { stringValue = null; diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs index 537c1fb03..3c93013a2 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs @@ -604,7 +604,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms } else { targetType = fallbackInstType; } - return (new NullCoalescingInstruction(kind, trueInst, fallbackInst), targetType); + return (new NullCoalescingInstruction(kind, trueInst, fallbackInst) { + UnderlyingResultType = trueInstTypeNonNullable.GetStackType() + }, targetType); } (ILInstruction, IType) ConvertComparison(CallInstruction invocation, ComparisonKind kind) diff --git a/ICSharpCode.Decompiler/TypeSystem/Accessibility.cs b/ICSharpCode.Decompiler/TypeSystem/Accessibility.cs index 5d7b5a13b..9cee2d55e 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Accessibility.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Accessibility.cs @@ -16,6 +16,9 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System.Diagnostics; +using ICSharpCode.Decompiler.Util; + namespace ICSharpCode.Decompiler.TypeSystem { /// @@ -23,8 +26,8 @@ namespace ICSharpCode.Decompiler.TypeSystem /// public enum Accessibility : byte { - // note: some code depends on the fact that these values are within the range 0-7 - + // Note: some code depends on the fact that these values are within the range 0-7 + /// /// The entity is completely inaccessible. This is used for C# explicit interface implementations. /// @@ -34,26 +37,83 @@ namespace ICSharpCode.Decompiler.TypeSystem /// Private, /// - /// The entity is accessible everywhere. + /// The entity is accessible in derived classes within the same assembly. + /// This corresponds to C# private protected. /// - Public, + ProtectedAndInternal, /// /// The entity is only accessible within the same class and in derived classes. /// Protected, /// - /// The entity is accessible within the same project content. + /// The entity is accessible within the same assembly. /// Internal, /// - /// The entity is accessible both everywhere in the project content, and in all derived classes. + /// The entity is accessible both everywhere in the assembly, and in all derived classes. + /// This corresponds to C# protected internal. /// - /// This corresponds to C# 'protected internal'. ProtectedOrInternal, /// - /// The entity is accessible in derived classes within the same project content. + /// The entity is accessible everywhere. /// - /// This corresponds to C# 'private protected'. - ProtectedAndInternal, + Public, + } + + public static class AccessibilityExtensions + { + // This code depends on the fact that the enum values are sorted similar to the partial order + // where an accessibility is smaller than another if it makes an entity visibible to a subset of the code: + // digraph Accessibilities { + // none -> private -> protected_and_internal -> protected -> protected_or_internal -> public; + // none -> private -> protected_and_internal -> internal -> protected_or_internal -> public; + // } + + /// + /// Gets whether a <= b in the partial order of accessibilities: + /// return true if b is accessible everywhere where a is accessible. + /// + public static bool LessThanOrEqual(this Accessibility a, Accessibility b) + { + // Exploit the enum order being similar to the partial order to dramatically simplify the logic here: + // protected vs. internal is the only pair for which the enum value order doesn't match the partial order + return a <= b && !(a == Accessibility.Protected && b == Accessibility.Internal); + } + + /// + /// Computes the intersection of the two accessibilities: + /// The result is accessible from any given point in the code + /// iff both a and b are accessible from that point. + /// + public static Accessibility Intersect(this Accessibility a, Accessibility b) + { + if (a > b) { + ExtensionMethods.Swap(ref a, ref b); + } + if (a == Accessibility.Protected && b == Accessibility.Internal) { + return Accessibility.ProtectedAndInternal; + } else { + Debug.Assert(!(a == Accessibility.Internal && b == Accessibility.Protected)); + return a; + } + } + + /// + /// Computes the union of the two accessibilities: + /// The result is accessible from any given point in the code + /// iff at least one of a or b is accessible from that point. + /// + public static Accessibility Union(this Accessibility a, Accessibility b) + { + if (a > b) { + ExtensionMethods.Swap(ref a, ref b); + } + if (a == Accessibility.Protected && b == Accessibility.Internal) { + return Accessibility.ProtectedOrInternal; + } else { + Debug.Assert(!(a == Accessibility.Internal && b == Accessibility.Protected)); + return b; + } + } } } diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataProperty.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataProperty.cs index 264bb5e02..cd98cf907 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataProperty.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataProperty.cs @@ -196,37 +196,10 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation return baseMember.Accessibility; } } - return MergePropertyAccessibility( + return AccessibilityExtensions.Union( this.Getter?.Accessibility ?? Accessibility.None, this.Setter?.Accessibility ?? Accessibility.None); } - - static internal Accessibility MergePropertyAccessibility(Accessibility left, Accessibility right) - { - if (left == Accessibility.Public || right == Accessibility.Public) - return Accessibility.Public; - - if (left == Accessibility.ProtectedOrInternal || right == Accessibility.ProtectedOrInternal) - return Accessibility.ProtectedOrInternal; - - if (left == Accessibility.Protected && right == Accessibility.Internal || - left == Accessibility.Internal && right == Accessibility.Protected) - return Accessibility.ProtectedOrInternal; - - if (left == Accessibility.Protected || right == Accessibility.Protected) - return Accessibility.Protected; - - if (left == Accessibility.Internal || right == Accessibility.Internal) - return Accessibility.Internal; - - if (left == Accessibility.ProtectedAndInternal || right == Accessibility.ProtectedAndInternal) - return Accessibility.ProtectedAndInternal; - - if (left == Accessibility.Private || right == Accessibility.Private) - return Accessibility.Private; - - return left; - } #endregion public bool IsStatic => AnyAccessor?.IsStatic ?? false; diff --git a/ILSpy/Analyzers/AnalyzerScope.cs b/ILSpy/Analyzers/AnalyzerScope.cs index 6a6e27344..68eac7f9d 100644 --- a/ILSpy/Analyzers/AnalyzerScope.cs +++ b/ILSpy/Analyzers/AnalyzerScope.cs @@ -45,7 +45,7 @@ namespace ICSharpCode.ILSpy.Analyzers public ITypeDefinition TypeScope => typeScope; - Accessibility memberAccessibility, typeAccessibility; + Accessibility effectiveAccessibility; public AnalyzerScope(AssemblyList assemblyList, IEntity entity) { @@ -53,13 +53,12 @@ namespace ICSharpCode.ILSpy.Analyzers AnalyzedSymbol = entity; if (entity is ITypeDefinition type) { typeScope = type; - memberAccessibility = Accessibility.None; + effectiveAccessibility = DetermineEffectiveAccessibility(ref typeScope); } else { typeScope = entity.DeclaringTypeDefinition; - memberAccessibility = entity.Accessibility; + effectiveAccessibility = DetermineEffectiveAccessibility(ref typeScope, entity.Accessibility); } - typeAccessibility = DetermineTypeAccessibility(ref typeScope); - IsLocal = memberAccessibility == Accessibility.Private || typeAccessibility == Accessibility.Private; + IsLocal = effectiveAccessibility.LessThanOrEqual(Accessibility.Private); } public IEnumerable GetModulesInScope(CancellationToken ct) @@ -67,10 +66,7 @@ namespace ICSharpCode.ILSpy.Analyzers if (IsLocal) return new[] { TypeScope.ParentModule.PEFile }; - if (memberAccessibility == Accessibility.Internal || - memberAccessibility == Accessibility.ProtectedOrInternal || - typeAccessibility == Accessibility.Internal || - typeAccessibility == Accessibility.ProtectedAndInternal) + if (effectiveAccessibility.LessThanOrEqual(Accessibility.Internal)) return GetModuleAndAnyFriends(TypeScope, ct); return GetReferencingModules(TypeScope.ParentModule.PEFile, ct); @@ -89,11 +85,7 @@ namespace ICSharpCode.ILSpy.Analyzers { if (IsLocal) { var typeSystem = new DecompilerTypeSystem(TypeScope.ParentModule.PEFile, TypeScope.ParentModule.PEFile.GetAssemblyResolver()); - ITypeDefinition scope = typeScope; - if (memberAccessibility != Accessibility.Private && typeScope.DeclaringTypeDefinition != null) { - scope = typeScope.DeclaringTypeDefinition; - } - foreach (var type in TreeTraversal.PreOrder(scope, t => t.NestedTypes)) { + foreach (var type in TreeTraversal.PreOrder(typeScope, t => t.NestedTypes)) { yield return type; } } else { @@ -106,23 +98,17 @@ namespace ICSharpCode.ILSpy.Analyzers } } - Accessibility DetermineTypeAccessibility(ref ITypeDefinition typeScope) + static Accessibility DetermineEffectiveAccessibility(ref ITypeDefinition typeScope, Accessibility memberAccessibility = Accessibility.Public) { - var typeAccessibility = typeScope.Accessibility; - while (typeScope.DeclaringType != null) { - Accessibility accessibility = typeScope.Accessibility; - if ((int)typeAccessibility > (int)accessibility) { - typeAccessibility = accessibility; - if (typeAccessibility == Accessibility.Private) - break; - } + Accessibility accessibility = memberAccessibility; + while (typeScope.DeclaringTypeDefinition != null && !accessibility.LessThanOrEqual(Accessibility.Private)) { + accessibility = accessibility.Intersect(typeScope.Accessibility); typeScope = typeScope.DeclaringTypeDefinition; } - - if ((int)typeAccessibility > (int)Accessibility.Internal) { - typeAccessibility = Accessibility.Internal; - } - return typeAccessibility; + // Once we reach a private entity, we leave the loop with typeScope set to the class that + // contains the private entity = the scope that needs to be searched. + // Otherwise (if we don't find a private entity) we return the top-level class. + return accessibility; } #region Find modules @@ -134,24 +120,39 @@ namespace ICSharpCode.ILSpy.Analyzers if (typeScope.TypeParameterCount > 0) reflectionTypeScopeName += "`" + typeScope.TypeParameterCount; - foreach (var assembly in AssemblyList.GetAssemblies()) { - ct.ThrowIfCancellationRequested(); - bool found = false; - var module = assembly.GetPEFileOrNull(); - if (module == null || !module.IsAssembly) - continue; - var resolver = assembly.GetAssemblyResolver(); - foreach (var reference in module.AssemblyReferences) { - using (LoadedAssembly.DisableAssemblyLoad()) { - if (resolver.Resolve(reference) == self) { - found = true; - break; + var toWalkFiles = new Stack(); + var checkedFiles = new HashSet(); + + toWalkFiles.Push(self); + checkedFiles.Add(self); + + do { + PEFile curFile = toWalkFiles.Pop(); + foreach (var assembly in AssemblyList.GetAssemblies()) { + ct.ThrowIfCancellationRequested(); + bool found = false; + var module = assembly.GetPEFileOrNull(); + if (module == null || !module.IsAssembly) + continue; + if (checkedFiles.Contains(module)) + continue; + var resolver = assembly.GetAssemblyResolver(); + foreach (var reference in module.AssemblyReferences) { + using (LoadedAssembly.DisableAssemblyLoad()) { + if (resolver.Resolve(reference) == curFile) { + found = true; + break; + } } } + if (found && checkedFiles.Add(module)) { + if (ModuleReferencesScopeType(module.Metadata, reflectionTypeScopeName, typeScope.Namespace)) + yield return module; + if (ModuleForwardsScopeType(module.Metadata, reflectionTypeScopeName, typeScope.Namespace)) + toWalkFiles.Push(module); + } } - if (found && ModuleReferencesScopeType(module.Metadata, reflectionTypeScopeName, typeScope.Namespace)) - yield return module; - } + } while (toWalkFiles.Count > 0); } IEnumerable GetModuleAndAnyFriends(ITypeDefinition typeScope, CancellationToken ct) @@ -198,6 +199,19 @@ namespace ICSharpCode.ILSpy.Analyzers } return hasRef; } + + bool ModuleForwardsScopeType(MetadataReader metadata, string typeScopeName, string typeScopeNamespace) + { + bool hasForward = false; + foreach (var h in metadata.ExportedTypes) { + var exportedType = metadata.GetExportedType(h); + if (exportedType.IsForwarder && metadata.StringComparer.Equals(exportedType.Name, typeScopeName) && metadata.StringComparer.Equals(exportedType.Namespace, typeScopeNamespace)) { + hasForward = true; + break; + } + } + return hasForward; + } #endregion } } diff --git a/ILSpy/Analyzers/AnalyzerTreeView.cs b/ILSpy/Analyzers/AnalyzerTreeView.cs index 4e6402b12..edbdf43cf 100644 --- a/ILSpy/Analyzers/AnalyzerTreeView.cs +++ b/ILSpy/Analyzers/AnalyzerTreeView.cs @@ -60,7 +60,7 @@ namespace ICSharpCode.ILSpy.Analyzers public void Show() { - AnalyzerPaneModel.Instance.IsVisible = true; + AnalyzerPaneModel.Instance.Show(); } public void Show(AnalyzerTreeNode node) diff --git a/ILSpy/AssemblyList.cs b/ILSpy/AssemblyList.cs index caa7f8ae3..6739bf1c5 100644 --- a/ILSpy/AssemblyList.cs +++ b/ILSpy/AssemblyList.cs @@ -70,6 +70,15 @@ namespace ICSharpCode.ILSpy } this.dirty = false; // OpenAssembly() sets dirty, so reset it afterwards } + + /// + /// Creates a copy of an assembly list. + /// + public AssemblyList(AssemblyList list, string newName) + : this(newName) + { + this.assemblies.AddRange(list.assemblies); + } /// /// Gets the loaded assemblies. This method is thread-safe. diff --git a/ILSpy/AssemblyListManager.cs b/ILSpy/AssemblyListManager.cs index 7c989f4eb..0c3404fc6 100644 --- a/ILSpy/AssemblyListManager.cs +++ b/ILSpy/AssemblyListManager.cs @@ -30,22 +30,26 @@ namespace ICSharpCode.ILSpy /// sealed class AssemblyListManager { + ILSpySettings spySettings; + public AssemblyListManager(ILSpySettings spySettings) { + this.spySettings = spySettings; XElement doc = spySettings["AssemblyLists"]; foreach (var list in doc.Elements("List")) { AssemblyLists.Add((string)list.Attribute("name")); } } - public readonly ObservableCollection AssemblyLists = new ObservableCollection(); + public ObservableCollection AssemblyLists { get; } = new ObservableCollection(); /// /// Loads an assembly list from the ILSpySettings. /// If no list with the specified name is found, the default list is loaded instead. /// - public AssemblyList LoadList(ILSpySettings spySettings, string listName) + public AssemblyList LoadList(ILSpySettings settings, string listName) { + this.spySettings = settings; AssemblyList list = DoLoadList(spySettings, listName); if (!AssemblyLists.Contains(list.ListName)) AssemblyLists.Add(list.ListName); @@ -62,13 +66,16 @@ namespace ICSharpCode.ILSpy } } } - XElement firstList = doc.Elements("List").FirstOrDefault(); - if (firstList != null) - return new AssemblyList(firstList); - else - return new AssemblyList(listName ?? DefaultListName); + return new AssemblyList(listName ?? DefaultListName); } - + + public bool CloneList(string selectedAssemblyList, string newListName) + { + var list = DoLoadList(spySettings, selectedAssemblyList); + var newList = new AssemblyList(list, newListName); + return CreateList(newList); + } + public const string DefaultListName = "(Default)"; /// diff --git a/ILSpy/Commands/DelegateCommand.cs b/ILSpy/Commands/DelegateCommand.cs new file mode 100644 index 000000000..4c0c033d2 --- /dev/null +++ b/ILSpy/Commands/DelegateCommand.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace ICSharpCode.ILSpy.Commands +{ + public class DelegateCommand : ICommand + { + private readonly Action action; + private readonly Func canExecute; + + public event EventHandler CanExecuteChanged { + add { CommandManager.RequerySuggested += value; } + remove { CommandManager.RequerySuggested -= value; } + } + + public DelegateCommand(Action action) + : this(action, _ => true) + { + } + + public DelegateCommand(Action action, Func canExecute) + { + this.action = action; + this.canExecute = canExecute; + } + + public bool CanExecute(object parameter) + { + return canExecute((T)parameter); + } + + public void Execute(object parameter) + { + action((T)parameter); + } + } +} diff --git a/ILSpy/Commands/ILSpyCommands.cs b/ILSpy/Commands/ILSpyCommands.cs index 4a2d7e98f..379432b7f 100644 --- a/ILSpy/Commands/ILSpyCommands.cs +++ b/ILSpy/Commands/ILSpyCommands.cs @@ -29,5 +29,6 @@ namespace ICSharpCode.ILSpy static class ILSpyCommands { public static readonly AnalyzeCommand Analyze = new AnalyzeCommand(); + public static readonly ManageAssemblyListsCommand ManageAssemblyListsCommand = new ManageAssemblyListsCommand(); } } diff --git a/ILSpy/Commands/OpenListCommand.cs b/ILSpy/Commands/ManageAssemblyListsCommand.cs similarity index 81% rename from ILSpy/Commands/OpenListCommand.cs rename to ILSpy/Commands/ManageAssemblyListsCommand.cs index 807171875..49effa4a7 100644 --- a/ILSpy/Commands/OpenListCommand.cs +++ b/ILSpy/Commands/ManageAssemblyListsCommand.cs @@ -21,15 +21,14 @@ using ICSharpCode.ILSpy.Properties; namespace ICSharpCode.ILSpy { - [ExportMainMenuCommand(Menu = nameof(Resources._File), Header = nameof(Resources.Open_List), MenuIcon = "Images/AssemblyList", MenuCategory = nameof(Resources.Open), MenuOrder = 1.7)] - sealed class OpenListCommand : SimpleCommand + [ExportMainMenuCommand(Menu = nameof(Resources._File), Header = nameof(Resources.ManageAssemblyLists), MenuIcon = "Images/AssemblyList", MenuCategory = nameof(Resources.Open), MenuOrder = 1.7)] + sealed class ManageAssemblyListsCommand : SimpleCommand { public override void Execute(object parameter) { - OpenListDialog dlg = new OpenListDialog(); + ManageAssemblyListsDialog dlg = new ManageAssemblyListsDialog(); dlg.Owner = MainWindow.Instance; - if (dlg.ShowDialog() == true) - MainWindow.Instance.ShowAssemblyList(dlg.SelectedListName); + dlg.ShowDialog(); } } } diff --git a/ILSpy/Commands/ShowDebugSteps.cs b/ILSpy/Commands/ShowDebugSteps.cs index 4f7c5fa8d..a5c3d98fd 100644 --- a/ILSpy/Commands/ShowDebugSteps.cs +++ b/ILSpy/Commands/ShowDebugSteps.cs @@ -11,7 +11,7 @@ namespace ICSharpCode.ILSpy.Commands { public override void Execute(object parameter) { - DebugStepsPaneModel.Instance.IsVisible = true; + DebugStepsPaneModel.Instance.Show(); } } } diff --git a/ILSpy/DebugInfo/DebugInfoUtils.cs b/ILSpy/DebugInfo/DebugInfoUtils.cs new file mode 100644 index 000000000..f52a790d7 --- /dev/null +++ b/ILSpy/DebugInfo/DebugInfoUtils.cs @@ -0,0 +1,113 @@ +// Copyright (c) 2018 Siegfried Pammer +// +// 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 System.IO; +using System.Linq; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; +using System.Runtime.InteropServices; +using ICSharpCode.Decompiler.DebugInfo; +using ICSharpCode.Decompiler.Metadata; + +namespace ICSharpCode.Decompiler.PdbProvider +{ + static class DebugInfoUtils + { + public static IDebugInfoProvider LoadSymbols(PEFile module) + { + try { + var reader = module.Reader; + // try to open portable pdb file/embedded pdb info: + if (TryOpenPortablePdb(module, out var provider, out var pdbFileName)) { + return new PortableDebugInfoProvider(pdbFileName, provider); + } else { + // search for pdb in same directory as dll + pdbFileName = Path.Combine(Path.GetDirectoryName(module.FileName), Path.GetFileNameWithoutExtension(module.FileName) + ".pdb"); + if (File.Exists(pdbFileName)) { + return new MonoCecilDebugInfoProvider(module, pdbFileName); + } + } + } catch (Exception ex) when (ex is BadImageFormatException || ex is COMException) { + // Ignore PDB load errors + } + return null; + } + + public static IDebugInfoProvider FromFile(PEFile module, string pdbFileName) + { + if (string.IsNullOrEmpty(pdbFileName)) + return null; + + Stream stream = OpenStream(pdbFileName); + if (stream == null) + return null; + + if (stream.Read(buffer, 0, buffer.Length) == LegacyPDBPrefix.Length + && System.Text.Encoding.ASCII.GetString(buffer) == LegacyPDBPrefix) { + stream.Position = 0; + return new MonoCecilDebugInfoProvider(module, pdbFileName); + } else { + stream.Position = 0; + var provider = MetadataReaderProvider.FromPortablePdbStream(stream); + return new PortableDebugInfoProvider(pdbFileName, provider); + } + } + + const string LegacyPDBPrefix = "Microsoft C/C++ MSF 7.00"; + static readonly byte[] buffer = new byte[LegacyPDBPrefix.Length]; + + static bool TryOpenPortablePdb(PEFile module, out MetadataReaderProvider provider, out string pdbFileName) + { + provider = null; + pdbFileName = null; + var reader = module.Reader; + foreach (var entry in reader.ReadDebugDirectory()) { + if (entry.IsPortableCodeView) { + return reader.TryOpenAssociatedPortablePdb(module.FileName, OpenStream, out provider, out pdbFileName); + } + if (entry.Type == DebugDirectoryEntryType.CodeView) { + string pdbDirectory = Path.GetDirectoryName(module.FileName); + pdbFileName = Path.Combine(pdbDirectory, Path.GetFileNameWithoutExtension(module.FileName) + ".pdb"); + if (File.Exists(pdbFileName)) { + Stream stream = OpenStream(pdbFileName); + if (stream.Read(buffer, 0, buffer.Length) == LegacyPDBPrefix.Length + && System.Text.Encoding.ASCII.GetString(buffer) == LegacyPDBPrefix) { + return false; + } + stream.Position = 0; + provider = MetadataReaderProvider.FromPortablePdbStream(stream); + return true; + } + } + } + return false; + } + + static Stream OpenStream(string fileName) + { + if (!File.Exists(fileName)) + return null; + var memory = new MemoryStream(); + using (var stream = File.OpenRead(fileName)) + stream.CopyTo(memory); + memory.Position = 0; + return memory; + } + } +} diff --git a/ILSpy/DebugInfo/PortableDebugInfoProvider.cs b/ILSpy/DebugInfo/PortableDebugInfoProvider.cs index c76cc2977..b811ce681 100644 --- a/ILSpy/DebugInfo/PortableDebugInfoProvider.cs +++ b/ILSpy/DebugInfo/PortableDebugInfoProvider.cs @@ -20,9 +20,8 @@ using System; using System.Collections.Generic; using System.Reflection.Metadata; using ICSharpCode.Decompiler.DebugInfo; -using ICSharpCode.Decompiler.Metadata; -namespace ICSharpCode.ILSpy.DebugInfo +namespace ICSharpCode.Decompiler.PdbProvider { class PortableDebugInfoProvider : IDebugInfoProvider { diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index 01e573f79..26c105b08 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -108,12 +108,13 @@ + - + @@ -135,11 +136,14 @@ - + + + CreateListDialog.xaml + - + DebugSteps.xaml @@ -313,18 +317,16 @@ - + NugetPackageBrowserDialog.xaml - + OpenFromGacDialog.xaml ResourceStringTable.xaml - - OpenListDialog.xaml - + DisplaySettingsPanel.xaml @@ -438,8 +440,8 @@ - - + + @@ -504,9 +506,9 @@ - - - + + + diff --git a/ILSpy/Languages/ILAstLanguage.cs b/ILSpy/Languages/ILAstLanguage.cs index 3d0fbdc8a..d7c100990 100644 --- a/ILSpy/Languages/ILAstLanguage.cs +++ b/ILSpy/Languages/ILAstLanguage.cs @@ -31,6 +31,7 @@ using ICSharpCode.Decompiler.TypeSystem; using SRM = System.Reflection.Metadata; using static System.Reflection.Metadata.PEReaderExtensions; +using ICSharpCode.ILSpy.ViewModels; namespace ICSharpCode.ILSpy { @@ -140,7 +141,7 @@ namespace ICSharpCode.ILSpy } } (output as ISmartTextOutput)?.AddButton(Images.ViewCode, "Show Steps", delegate { - DebugSteps.Show(); + DebugStepsPaneModel.Instance.Show(); }); output.WriteLine(); il.WriteTo(output, DebugSteps.Options); diff --git a/ILSpy/LoadedAssembly.cs b/ILSpy/LoadedAssembly.cs index 78b3e1854..46fe60622 100644 --- a/ILSpy/LoadedAssembly.cs +++ b/ILSpy/LoadedAssembly.cs @@ -28,10 +28,9 @@ using System.Threading; using System.Threading.Tasks; using ICSharpCode.Decompiler.DebugInfo; using ICSharpCode.Decompiler.Metadata; -using ICSharpCode.Decompiler.PdbProvider.Cecil; +using ICSharpCode.Decompiler.PdbProvider; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem.Implementation; -using ICSharpCode.ILSpy.DebugInfo; using ICSharpCode.ILSpy.Options; namespace ICSharpCode.ILSpy @@ -167,7 +166,7 @@ namespace ICSharpCode.ILSpy if (DecompilerSettingsPanel.CurrentDecompilerSettings.UseDebugSymbols) { try { - LoadSymbols(module); + debugInfoProvider = DebugInfoUtils.LoadSymbols(module); } catch (IOException) { } catch (UnauthorizedAccessException) { } catch (InvalidOperationException) { @@ -180,70 +179,6 @@ namespace ICSharpCode.ILSpy return module; } - void LoadSymbols(PEFile module) - { - try { - var reader = module.Reader; - // try to open portable pdb file/embedded pdb info: - if (TryOpenPortablePdb(module, out var provider, out var pdbFileName)) { - debugInfoProvider = new PortableDebugInfoProvider(pdbFileName, provider); - } else { - // search for pdb in same directory as dll - string pdbDirectory = Path.GetDirectoryName(fileName); - pdbFileName = Path.Combine(pdbDirectory, Path.GetFileNameWithoutExtension(fileName) + ".pdb"); - if (File.Exists(pdbFileName)) { - debugInfoProvider = new MonoCecilDebugInfoProvider(module, pdbFileName); - return; - } - - // TODO: use symbol cache, get symbols from microsoft - } - } catch (Exception ex) when (ex is BadImageFormatException || ex is COMException) { - // Ignore PDB load errors - } - } - - const string LegacyPDBPrefix = "Microsoft C/C++ MSF 7.00"; - byte[] buffer = new byte[LegacyPDBPrefix.Length]; - - bool TryOpenPortablePdb(PEFile module, out MetadataReaderProvider provider, out string pdbFileName) - { - provider = null; - pdbFileName = null; - var reader = module.Reader; - foreach (var entry in reader.ReadDebugDirectory()) { - if (entry.IsPortableCodeView) { - return reader.TryOpenAssociatedPortablePdb(fileName, OpenStream, out provider, out pdbFileName); - } - if (entry.Type == DebugDirectoryEntryType.CodeView) { - string pdbDirectory = Path.GetDirectoryName(fileName); - pdbFileName = Path.Combine(pdbDirectory, Path.GetFileNameWithoutExtension(fileName) + ".pdb"); - if (File.Exists(pdbFileName)) { - Stream stream = OpenStream(pdbFileName); - if (stream.Read(buffer, 0, buffer.Length) == LegacyPDBPrefix.Length - && System.Text.Encoding.ASCII.GetString(buffer) == LegacyPDBPrefix) { - return false; - } - stream.Position = 0; - provider = MetadataReaderProvider.FromPortablePdbStream(stream); - return true; - } - } - } - return false; - } - - Stream OpenStream(string fileName) - { - if (!File.Exists(fileName)) - return null; - var memory = new MemoryStream(); - using (var stream = File.OpenRead(fileName)) - stream.CopyTo(memory); - memory.Position = 0; - return memory; - } - [ThreadStatic] static int assemblyLoadDisableCount; diff --git a/ILSpy/MainWindow.xaml b/ILSpy/MainWindow.xaml index 77c6086b8..3eb590066 100644 --- a/ILSpy/MainWindow.xaml +++ b/ILSpy/MainWindow.xaml @@ -6,7 +6,6 @@ xmlns:local="clr-namespace:ICSharpCode.ILSpy" xmlns:avalondock="http://schemas.xceed.com/wpf/xaml/avalondock" xmlns:controls="clr-namespace:ICSharpCode.ILSpy.Controls" - xmlns:avalondockproperties="clr-namespace:Xceed.Wpf.AvalonDock.Properties;assembly=Xceed.Wpf.AvalonDock" xmlns:docking="clr-namespace:ICSharpCode.ILSpy.Docking" xmlns:textview="clr-namespace:ICSharpCode.ILSpy.TextView" xmlns:analyzers="clr-namespace:ICSharpCode.ILSpy.Analyzers" @@ -121,6 +120,13 @@ + + + diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index d294cf5d2..546c45ac7 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -24,6 +24,7 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection.Metadata; +using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; using System.Windows; @@ -56,6 +57,7 @@ namespace ICSharpCode.ILSpy { public DockWorkspace Workspace { get; set; } public SessionSettings SessionSettings { get; set; } + public AssemblyListManager AssemblyListManager { get; set; } } /// @@ -68,8 +70,6 @@ namespace ICSharpCode.ILSpy readonly NavigationHistory history = new NavigationHistory(); ILSpySettings spySettingsForMainWindow_Loaded; internal SessionSettings sessionSettings; - - internal AssemblyListManager assemblyListManager; AssemblyList assemblyList; AssemblyListTreeNode assemblyListTreeNode; @@ -83,6 +83,8 @@ namespace ICSharpCode.ILSpy get { return sessionSettings; } } + internal AssemblyListManager AssemblyListManager { get; } + public SharpTreeView treeView { get { return FindResource("TreeView") as SharpTreeView; @@ -107,19 +109,21 @@ namespace ICSharpCode.ILSpy var spySettings = ILSpySettings.Load(); this.spySettingsForMainWindow_Loaded = spySettings; this.sessionSettings = new SessionSettings(spySettings); - this.assemblyListManager = new AssemblyListManager(spySettings); + this.AssemblyListManager = new AssemblyListManager(spySettings); this.Icon = new BitmapImage(new Uri("pack://application:,,,/ILSpy;component/images/ILSpy.ico")); this.DataContext = new MainWindowDataContext { Workspace = DockWorkspace.Instance, - SessionSettings = sessionSettings + SessionSettings = sessionSettings, + AssemblyListManager = AssemblyListManager }; DockWorkspace.Instance.LoadSettings(sessionSettings); InitializeComponent(); DockWorkspace.Instance.InitializeLayout(DockManager); sessionSettings.FilterSettings.PropertyChanged += filterSettings_PropertyChanged; + sessionSettings.PropertyChanged += SessionSettings_PropertyChanged; InitMainMenu(); InitToolbar(); @@ -128,6 +132,13 @@ namespace ICSharpCode.ILSpy this.Loaded += MainWindow_Loaded; } + private void SessionSettings_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == "ActiveAssemblyList") { + ShowAssemblyList(sessionSettings.ActiveAssemblyList); + } + } + void SetWindowBounds(Rect bounds) { this.Left = bounds.Left; @@ -376,7 +387,7 @@ namespace ICSharpCode.ILSpy } else if (spySettings != null) { SharpTreeNode node = null; if (activeTreeViewPath?.Length > 0) { - foreach (var asm in assemblyList.GetAssemblies()) { + foreach (var asm in CurrentAssemblyList.GetAssemblies()) { if (asm.FileName == activeTreeViewPath[0]) { // FindNodeByPath() blocks the UI if the assembly is not yet loaded, // so use an async wait instead. @@ -466,10 +477,10 @@ namespace ICSharpCode.ILSpy if (loadPreviousAssemblies) { // Load AssemblyList only in Loaded event so that WPF is initialized before we start the CPU-heavy stuff. // This makes the UI come up a bit faster. - this.assemblyList = assemblyListManager.LoadList(spySettings, sessionSettings.ActiveAssemblyList); + this.assemblyList = AssemblyListManager.LoadList(spySettings, sessionSettings.ActiveAssemblyList); } else { this.assemblyList = new AssemblyList(AssemblyListManager.DefaultListName); - assemblyListManager.ClearAll(); + AssemblyListManager.ClearAll(); } HandleCommandLineArguments(App.CommandLineArguments); @@ -584,8 +595,7 @@ namespace ICSharpCode.ILSpy public void ShowAssemblyList(string name) { - ILSpySettings settings = ILSpySettings.Load(); - AssemblyList list = this.assemblyListManager.LoadList(settings, name); + AssemblyList list = this.AssemblyListManager.LoadList(ILSpySettings.Load(), name); //Only load a new list when it is a different one if (list.ListName != CurrentAssemblyList.ListName) { ShowAssemblyList(list); @@ -906,7 +916,7 @@ namespace ICSharpCode.ILSpy try { refreshInProgress = true; var path = GetPathForNode(treeView.SelectedItem as SharpTreeNode); - ShowAssemblyList(assemblyListManager.LoadList(ILSpySettings.Load(), assemblyList.ListName)); + ShowAssemblyList(AssemblyListManager.LoadList(ILSpySettings.Load(), assemblyList.ListName)); SelectNode(FindNodeByPath(path, true)); } finally { refreshInProgress = false; @@ -915,7 +925,7 @@ namespace ICSharpCode.ILSpy void SearchCommandExecuted(object sender, ExecutedRoutedEventArgs e) { - SearchPaneModel.Instance.IsVisible = true; + SearchPaneModel.Instance.Show(); } #endregion diff --git a/ILSpy/OpenListDialog.xaml b/ILSpy/OpenListDialog.xaml deleted file mode 100644 index 99c5864e1..000000000 --- a/ILSpy/OpenListDialog.xaml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - +