commit 3d7a16393f479c4711f9cdbf20eaf8c77607ea2b Author: Merijn Hendriks Date: Mon Sep 20 18:20:01 2021 +0200 Initial project diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..63fd71d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,112 @@ +root = true + +[*] +charset = utf-8 +#end_of_line = +indent_size = 4 +indent_style = tab +tab_width = 4 + +[*.json] + +[app.config] + +[*.yml] +indent_size = 2 +indent_style = space + +[*.{proj,csproj,vbproj,props,targets,resx,vsixmanifest}] +indent_size = 2 +indent_style = space + +[app.manifest] +indent_size = 2 +indent_style = space + +[*.xml] + +[*.xaml] +indent_style = space + +[*.{cs,vb}] +insert_final_newline = true + +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = true +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_object_initializer = true:suggestion +dotnet_style_predefined_type_for_locals_parameters_members = true:none +dotnet_style_predefined_type_for_member_access = true:none +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_require_accessibility_modifiers = never:info + +[*.cs] +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = false +csharp_indent_labels = flush_left +csharp_indent_switch_labels = false +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = none +csharp_new_line_between_query_expression_clauses = true +csharp_prefer_braces = false +csharp_prefer_simple_default_expression = true:suggestion +#csharp_preferred_modifier_order = +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = +csharp_space_between_square_brackets = false +csharp_style_conditional_delegate_call = true:suggestion +csharp_style_deconstructed_variable_declaration = false:none +csharp_style_expression_bodied_accessors = true:suggestion +csharp_style_expression_bodied_constructors = true:suggestion +csharp_style_expression_bodied_indexers = true:suggestion +csharp_style_expression_bodied_methods = true:suggestion +csharp_style_expression_bodied_operators = true:suggestion +csharp_style_expression_bodied_properties = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_style_var_elsewhere = true:suggestion +csharp_style_var_for_built_in_types = false:none +csharp_style_var_when_type_is_apparent = true:suggestion + +[*.vb] +#visual_basic_preferred_modifier_order = diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..46c1a10 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.vs/ +bin/ +obj/ +*.user +_ReSharper*/ +*.ReSharper +*_wpftmp.csproj +launchSettings.json +.idea/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..8f6129e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,21 @@ +[submodule "Plugins/ILSpy.Decompiler/NRefactory"] + path = Extensions/ILSpy.Decompiler/NRefactory + url = https://github.com/dnSpy/nrefactory.git +[submodule "Libraries/ICSharpCode.TreeView"] + path = Libraries/ICSharpCode.TreeView + url = https://github.com/dnSpy/ICSharpCode.TreeView.git +[submodule "Plugins/ILSpy.Decompiler/ICSharpCode.Decompiler"] + path = Extensions/ILSpy.Decompiler/ICSharpCode.Decompiler + url = https://github.com/dnSpy/ILSpy.git +[submodule "dnSpy/dnSpy.Images"] + path = dnSpy/dnSpy.Images + url = https://github.com/dnSpy/dnSpy.Images.git +[submodule "Extensions/dnSpy.Debugger/netcorefiles"] + path = Extensions/dnSpy.Debugger/netcorefiles + url = https://github.com/dnSpy/netcorefiles.git +[submodule "dnSpy/Roslyn/Roslyn.ExpressionCompiler"] + path = dnSpy/Roslyn/Roslyn.ExpressionCompiler + url = https://github.com/dnSpy/Roslyn.ExpressionCompiler.git +[submodule "Extensions/dnSpy.Debugger/Mono.Debugger.Soft"] + path = Extensions/dnSpy.Debugger/Mono.Debugger.Soft + url = https://github.com/dnSpy/Mono.Debugger.Soft.git diff --git a/Build/AppHostPatcher/AppHostPatcher.csproj b/Build/AppHostPatcher/AppHostPatcher.csproj new file mode 100644 index 0000000..95a4764 --- /dev/null +++ b/Build/AppHostPatcher/AppHostPatcher.csproj @@ -0,0 +1,18 @@ + + + + + + $(DnSpyAssemblyCopyright) + $(DnSpyAssemblyVersion) + $(DnSpyAssemblyInformationalVersion) + + True + ..\..\dnSpy.snk + enable + + $(DnSpyRuntimeIdentifiers) + Exe + + + diff --git a/Build/AppHostPatcher/Program.cs b/Build/AppHostPatcher/Program.cs new file mode 100644 index 0000000..de4572a --- /dev/null +++ b/Build/AppHostPatcher/Program.cs @@ -0,0 +1,133 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; +using System.Diagnostics; +using System.IO; +using System.Text; + +namespace AppHostPatcher { + class Program { + static void Usage() { + Console.WriteLine("apphostpatcher "); + Console.WriteLine("apphostpatcher "); + Console.WriteLine("apphostpatcher -d "); + Console.WriteLine("example: apphostpatcher my.exe -d bin"); + } + + const int maxPathBytes = 1024; + + static string ChangeExecutableExtension(string apphostExe) => + // Windows apphosts have an .exe extension. Don't call Path.ChangeExtension() unless it's guaranteed + // to have an .exe extension, eg. 'some.file' => 'some.file.dll', not 'some.dll' + apphostExe.EndsWith(".exe", StringComparison.OrdinalIgnoreCase) ? Path.ChangeExtension(apphostExe, ".dll") : apphostExe + ".dll"; + + static string GetPathSeparator(string apphostExe) => + apphostExe.EndsWith(".exe", StringComparison.OrdinalIgnoreCase) ? @"\" : "/"; + + static int Main(string[] args) { + try { + string apphostExe, origPath, newPath; + if (args.Length == 3) { + if (args[1] == "-d") { + apphostExe = args[0]; + origPath = Path.GetFileName(ChangeExecutableExtension(apphostExe)); + newPath = args[2] + GetPathSeparator(apphostExe) + origPath; + } + else { + apphostExe = args[0]; + origPath = args[1]; + newPath = args[2]; + } + } + else if (args.Length == 2) { + apphostExe = args[0]; + origPath = Path.GetFileName(ChangeExecutableExtension(apphostExe)); + newPath = args[1]; + } + else { + Usage(); + return 1; + } + if (!File.Exists(apphostExe)) { + Console.WriteLine($"Apphost '{apphostExe}' does not exist"); + return 1; + } + if (origPath == string.Empty) { + Console.WriteLine("Original path is empty"); + return 1; + } + var origPathBytes = Encoding.UTF8.GetBytes(origPath + "\0"); + Debug.Assert(origPathBytes.Length > 0); + var newPathBytes = Encoding.UTF8.GetBytes(newPath + "\0"); + if (origPathBytes.Length > maxPathBytes) { + Console.WriteLine($"Original path is too long"); + return 1; + } + if (newPathBytes.Length > maxPathBytes) { + Console.WriteLine($"New path is too long"); + return 1; + } + + var apphostExeBytes = File.ReadAllBytes(apphostExe); + int offset = GetOffset(apphostExeBytes, origPathBytes); + if (offset < 0) { + Console.WriteLine($"Could not find original path '{origPath}'"); + return 1; + } + if (offset + newPathBytes.Length > apphostExeBytes.Length) { + Console.WriteLine($"New path is too long: {newPath}"); + return 1; + } + for (int i = 0; i < newPathBytes.Length; i++) + apphostExeBytes[offset + i] = newPathBytes[i]; + File.WriteAllBytes(apphostExe, apphostExeBytes); + return 0; + } + catch (Exception ex) { + Console.WriteLine(ex.ToString()); + return 1; + } + } + + static int GetOffset(byte[] bytes, byte[] pattern) { + int si = 0; + var b = pattern[0]; + while (si < bytes.Length) { + si = Array.IndexOf(bytes, b, si); + if (si < 0) + break; + if (Match(bytes, si, pattern)) + return si; + si++; + } + return -1; + } + + static bool Match(byte[] bytes, int index, byte[] pattern) { + if (index + pattern.Length > bytes.Length) + return false; + for (int i = 0; i < pattern.Length; i++) { + if (bytes[index + i] != pattern[i]) + return false; + } + return true; + } + } +} diff --git a/Build/ConvertToNetstandardReferences/ConvertToNetstandardReferences.cs b/Build/ConvertToNetstandardReferences/ConvertToNetstandardReferences.cs new file mode 100644 index 0000000..f2bdcf0 --- /dev/null +++ b/Build/ConvertToNetstandardReferences/ConvertToNetstandardReferences.cs @@ -0,0 +1,202 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using dnlib.DotNet; +using dnlib.DotNet.Writer; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace ConvertToNetstandardReferences { + public sealed class ConvertToNetstandardReferences : Task { + // Increment it if something changes so the files are re-created + const string VERSION = "cnsrefs_v1"; + +#pragma warning disable CS8618 // Non-nullable field is uninitialized. + [Required] + public string DestinationDirectory { get; set; } + + [Required] + public ITaskItem[] ReferencePath { get; set; } + + [Output] + public ITaskItem[] OutputReferencePath { get; private set; } +#pragma warning restore CS8618 // Non-nullable field is uninitialized. + + bool ShouldPatchAssembly(string simpleName) { + if (simpleName.StartsWith("Microsoft.VisualStudio.")) + return true; + + return false; + } + + static bool IsPublic(TypeDef type) { + while (type is not null) { + if (!type.IsPublic && !type.IsNestedPublic) + return false; + type = type.DeclaringType; + } + return true; + } + + static bool IsPublic(ExportedType type) { + while (type is not null) { + if (!type.IsPublic && !type.IsNestedPublic) + return false; + type = type.DeclaringType; + } + return true; + } + + public override bool Execute() { + if (string.IsNullOrWhiteSpace(DestinationDirectory)) { + Log.LogMessageFromText(nameof(DestinationDirectory) + " is an empty string", MessageImportance.High); + return false; + } + + using (var assemblyFactory = new AssemblyFactory(ReferencePath.Select(a => a.ItemSpec))) { + AssemblyRef? netstandardAsmRef = null; + AssemblyDef? netstandardAsm = null; + var typeComparer = new TypeEqualityComparer(SigComparerOptions.DontCompareTypeScope); + var netstandardTypes = new HashSet(typeComparer); + OutputReferencePath = new ITaskItem[ReferencePath.Length]; + for (int i = 0; i < ReferencePath.Length; i++) { + var file = ReferencePath[i]; + OutputReferencePath[i] = file; + var filename = file.ItemSpec; + var fileExt = Path.GetExtension(filename); + var asmSimpleName = Path.GetFileNameWithoutExtension(filename); + if (!ShouldPatchAssembly(asmSimpleName)) + continue; + if (!File.Exists(filename)) { + Log.LogMessageFromText($"File does not exist: {filename}", MessageImportance.High); + return false; + } + + var patchDir = DestinationDirectory; + Directory.CreateDirectory(patchDir); + + var fileInfo = new FileInfo(filename); + long filesize = fileInfo.Length; + long writeTime = fileInfo.LastWriteTimeUtc.ToBinary(); + + var extraInfo = $"_{VERSION} {filesize} {writeTime}_"; + var patchedFilename = Path.Combine(patchDir, asmSimpleName + extraInfo + fileExt); + if (StringComparer.OrdinalIgnoreCase.Equals(patchedFilename, filename)) + continue; + + if (!File.Exists(patchedFilename)) { + var asm = assemblyFactory.Resolve(asmSimpleName); + if (asm is null) + throw new Exception($"Couldn't resolve assembly {filename}"); + var mod = (ModuleDefMD)asm.ManifestModule; + if (!ShouldPatchAssembly(mod)) + continue; + + if (netstandardAsm is null) { + netstandardAsm = assemblyFactory.Resolve("netstandard"); + if (netstandardAsm is null) + throw new Exception("Couldn't find a netstandard file"); + netstandardAsmRef = netstandardAsm.ToAssemblyRef(); + foreach (var type in netstandardAsm.ManifestModule.GetTypes()) { + if (type.IsGlobalModuleType) + continue; + if (IsPublic(type)) + netstandardTypes.Add(type); + } + foreach (var type in netstandardAsm.ManifestModule.ExportedTypes) + netstandardTypes.Add(type); + } + + for (uint rid = 1; ; rid++) { + var tr = mod.ResolveTypeRef(rid); + if (tr is null) + break; + if (!netstandardTypes.Contains(tr)) + continue; + if (tr.ResolutionScope is AssemblyRef asmRef && CanReplaceAssemblyRef(asmRef)) + tr.ResolutionScope = netstandardAsmRef; + } + + var options = new ModuleWriterOptions(mod); + mod.Write(patchedFilename, options); + + var xmlDocFile = Path.ChangeExtension(filename, "xml"); + if (File.Exists(xmlDocFile)) { + var newXmlDocFile = Path.ChangeExtension(patchedFilename, "xml"); + if (File.Exists(newXmlDocFile)) + File.Delete(newXmlDocFile); + File.Copy(xmlDocFile, newXmlDocFile); + } + } + + OutputReferencePath[i] = new TaskItem(patchedFilename); + } + + return true; + } + } + + static bool CanReplaceAssemblyRef(AssemblyRef asmRef) => true; + + static bool ShouldPatchAssembly(ModuleDefMD mod) { + foreach (var asmRef in mod.GetAssemblyRefs()) { + string name = asmRef.Name; + if (name == "netstandard") + return false; + } + return true; + } + } + + sealed class AssemblyFactory : IAssemblyResolver, IDisposable { + readonly Dictionary modules; + readonly Dictionary nameToPath; + readonly ModuleContext context; + + public AssemblyFactory(IEnumerable filenames) { + modules = new Dictionary(StringComparer.Ordinal); + nameToPath = filenames.ToDictionary(key => Path.GetFileNameWithoutExtension(key), value => value, StringComparer.Ordinal); + context = new ModuleContext(this, new Resolver(this)); + } + + AssemblyDef? IAssemblyResolver.Resolve(IAssembly assembly, ModuleDef sourceModule) => + Resolve(assembly.Name); + + public AssemblyDef? Resolve(string name) { + if (modules.TryGetValue(name, out var module)) + return module.Assembly; + if (!nameToPath.TryGetValue(name, out var path)) + return null; + var options = new ModuleCreationOptions(context); + options.TryToLoadPdbFromDisk = false; + module = ModuleDefMD.Load(path, options); + modules.Add(name, module); + return module.Assembly ?? throw new InvalidOperationException("It's a netmodule"); + } + + public void Dispose() { + foreach (var module in modules) + module.Value.Dispose(); + } + } +} diff --git a/Build/ConvertToNetstandardReferences/ConvertToNetstandardReferences.csproj b/Build/ConvertToNetstandardReferences/ConvertToNetstandardReferences.csproj new file mode 100644 index 0000000..1393973 --- /dev/null +++ b/Build/ConvertToNetstandardReferences/ConvertToNetstandardReferences.csproj @@ -0,0 +1,25 @@ + + + + + + + netstandard2.0;$(TargetFrameworks) + $(DnSpyAssemblyCopyright) + $(DnSpyAssemblyVersion) + $(DnSpyAssemblyInformationalVersion) + + True + ..\..\dnSpy.snk + true + enable + + + + + + + + + diff --git a/Build/ConvertToNetstandardReferences/ConvertToNetstandardReferences.tasks b/Build/ConvertToNetstandardReferences/ConvertToNetstandardReferences.tasks new file mode 100644 index 0000000..5d80ad4 --- /dev/null +++ b/Build/ConvertToNetstandardReferences/ConvertToNetstandardReferences.tasks @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/Build/MakeEverythingPublic/IVTPatcher.cs b/Build/MakeEverythingPublic/IVTPatcher.cs new file mode 100644 index 0000000..564e2df --- /dev/null +++ b/Build/MakeEverythingPublic/IVTPatcher.cs @@ -0,0 +1,194 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +// This code patches an assembly's System.Runtime.CompilerServices.InternalsVisibleToAttribute +// so the string passed to the attribute constructor is the name of another assembly. +// If there's no such attribute or if the new string doesn't fit in the old string, this code fails. +// If it happens, use a smaller public key and/or a shorter assembly name and use no spaces +// or don't use PublicKey=xxxx... (since Roslyn C# compiler seems to ignore the public key). +// +// A more generic patcher would rewrite some of the metadata tables but this isn't needed since +// we only use it to patch Roslyn assemblies which contain a ton of IVT attributes. +// +// PERF is the same as copying a file since it just patches the data in memory, nothing is rewritten. + +using System; +using dnlib.DotNet; +using dnlib.DotNet.MD; +using dnlib.IO; + +namespace MakeEverythingPublic { + enum IVTPatcherResult { + OK, + NoCustomAttributes, + NoIVTs, + IVTBlobTooSmall, + } + + struct IVTPatcher { + // Prefer overwriting IVTs with this public key since they're just test assemblies + const string ROSLYN_OPEN_SOURCE_PUBLIC_KEY = "002400000480000094000000060200000024000052534131000400000100010055e0217eb635f69281051f9a823e0c7edd90f28063eb6c7a742a19b4f6139778ee0af438f47aed3b6e9f99838aa8dba689c7a71ddb860c96d923830b57bbd5cd6119406ddb9b002cf1c723bf272d6acbb7129e9d6dd5a5309c94e0ff4b2c884d45a55f475cd7dba59198086f61f5a8c8b5e601c0edbf269733f6f578fc8579c2"; + + readonly byte[] data; + readonly Metadata md; + readonly byte[] ivtBlob; + + public IVTPatcher(byte[] data, Metadata md, byte[] ivtBlob) { + this.data = data; + this.md = md; + this.ivtBlob = ivtBlob; + } + + public IVTPatcherResult Patch() { + var rids = md.GetCustomAttributeRidList(Table.Assembly, 1); + if (rids.Count == 0) + return IVTPatcherResult.NoCustomAttributes; + + if (FindIVT(rids, out var foundIVT, out uint ivtBlobOffset)) { + Array.Copy(ivtBlob, 0, data, ivtBlobOffset, ivtBlob.Length); + return IVTPatcherResult.OK; + } + + if (!foundIVT) + return IVTPatcherResult.NoIVTs; + return IVTPatcherResult.IVTBlobTooSmall; + } + + bool FindIVT(RidList rids, out bool foundIVT, out uint ivtBlobDataOffset) { + ivtBlobDataOffset = 0; + foundIVT = false; + uint otherIVTBlobOffset = uint.MaxValue; + var blobStream = md.BlobStream.CreateReader(); + var tbl = md.TablesStream.CustomAttributeTable; + uint baseOffset = (uint)tbl.StartOffset; + var columnType = tbl.Columns[1]; + var columnValue = tbl.Columns[2]; + for (int i = 0; i < rids.Count; i++) { + uint rid = rids[i]; + uint offset = baseOffset + (rid - 1) * tbl.RowSize; + uint type = ReadColumn(columnType, offset); + if (!IsIVTCtor(type)) + continue; + foundIVT = true; + uint blobOffset = ReadColumn(columnValue, offset); + if (blobOffset + ivtBlob.Length > blobStream.Length) + continue; + blobStream.Position = blobOffset; + if (!blobStream.TryReadCompressedUInt32(out uint len)) + continue; + var compressedSize = blobStream.Position - blobOffset; + if (compressedSize + len < ivtBlob.Length) + continue; + if (!ParseIVTBlob(ref blobStream, blobStream.Position + len, out var publicKeyString)) + continue; + if (StringComparer.OrdinalIgnoreCase.Equals(publicKeyString, ROSLYN_OPEN_SOURCE_PUBLIC_KEY)) { + ivtBlobDataOffset = (uint)md.BlobStream.StartOffset + blobOffset; + return true; + } + else + otherIVTBlobOffset = (uint)md.BlobStream.StartOffset + blobOffset; + } + if (otherIVTBlobOffset != uint.MaxValue) { + ivtBlobDataOffset = otherIVTBlobOffset; + return true; + } + + return false; + } + + static bool ParseIVTBlob(ref DataReader reader, uint end, out string? publicKeyString) { + publicKeyString = null; + if ((ulong)reader.Position + 2 > end) + return false; + if (reader.ReadUInt16() != 1) + return false; + if (!reader.TryReadCompressedUInt32(out uint len) || (ulong)reader.Position + len >= end) + return false; + var s = reader.ReadUtf8String((int)len); + const string PublicKeyPattern = "PublicKey="; + int index = s.IndexOf(PublicKeyPattern, StringComparison.OrdinalIgnoreCase); + if (index >= 0) + publicKeyString = s.Substring(index + PublicKeyPattern.Length).Trim(); + return true; + } + + bool IsIVTCtor(uint codedType) { + if (!CodedToken.CustomAttributeType.Decode(codedType, out MDToken ctor)) + return false; + + switch (ctor.Table) { + case Table.Method: + uint declTypeDefToken = md.GetOwnerTypeOfMethod(ctor.Rid); + return IsIVT_TypeDef(declTypeDefToken); + + case Table.MemberRef: + if (!md.TablesStream.TryReadMemberRefRow(ctor.Rid, out var memberRefRow)) + return false; + if (!CodedToken.MemberRefParent.Decode(memberRefRow.Class, out MDToken parentToken)) + return false; + switch (parentToken.Table) { + case Table.TypeDef: + return IsIVT_TypeDef(parentToken.Rid); + + case Table.TypeRef: + return IsIVT_TypeRef(parentToken.Rid); + + case Table.TypeSpec: + default: + return false; + } + + default: + return false; + } + } + + bool IsIVT_TypeRef(uint typeRefRid) { + if (!md.TablesStream.TryReadTypeRefRow(typeRefRid, out var typeRefRow)) + return false; + if (!CodedToken.ResolutionScope.Decode(typeRefRow.ResolutionScope, out MDToken scope) || scope.Table == Table.TypeRef) + return false; + return IsIVTCtor(typeRefRow.Namespace, typeRefRow.Name); + } + + bool IsIVT_TypeDef(uint typeDefRid) { + if (!md.TablesStream.TryReadTypeDefRow(typeDefRid, out var typeDefRow)) + return false; + if ((typeDefRow.Flags & (uint)TypeAttributes.VisibilityMask) >= (uint)TypeAttributes.NestedPublic) + return false; + return IsIVTCtor(typeDefRow.Namespace, typeDefRow.Name); + } + + bool IsIVTCtor(uint @namespace, uint name) => + md.StringsStream.ReadNoNull(name) == InternalsVisibleToAttribute && + md.StringsStream.ReadNoNull(@namespace) == System_Runtime_CompilerServices; + static readonly UTF8String System_Runtime_CompilerServices = new UTF8String("System.Runtime.CompilerServices"); + static readonly UTF8String InternalsVisibleToAttribute = new UTF8String("InternalsVisibleToAttribute"); + + uint ReadColumn(ColumnInfo column, uint columnOffset) { + columnOffset += (uint)column.Offset; + switch (column.Size) { + case 1: return data[columnOffset]; + case 2: return data[columnOffset++] | ((uint)data[columnOffset] << 8); + case 4: return data[columnOffset++] | ((uint)data[columnOffset++] << 8) | ((uint)data[columnOffset++] << 16) | ((uint)data[columnOffset] << 24); + default: throw new InvalidOperationException(); + } + } + } +} diff --git a/Build/MakeEverythingPublic/MakeEverythingPublic.cs b/Build/MakeEverythingPublic/MakeEverythingPublic.cs new file mode 100644 index 0000000..44b5a54 --- /dev/null +++ b/Build/MakeEverythingPublic/MakeEverythingPublic.cs @@ -0,0 +1,211 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; +using dnlib.DotNet.MD; +using dnlib.PE; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace MakeEverythingPublic { + public sealed class MakeEverythingPublic : Task { + // Increment it if something changes so the files are re-created + const string VERSION = "v1"; + +#pragma warning disable CS8618 // Non-nullable field is uninitialized. + [Required] + public string IVTString { get; set; } + + [Required] + public string DestinationDirectory { get; set; } + + [Required] + public string AssembliesToMakePublic { get; set; } + + [Required] + public ITaskItem[] ReferencePath { get; set; } + + [Output] + public ITaskItem[] OutputReferencePath { get; private set; } +#pragma warning restore CS8618 // Non-nullable field is uninitialized. + + public override bool Execute() { + if (string.IsNullOrWhiteSpace(IVTString)) { + Log.LogMessageFromText(nameof(IVTString) + " is an empty string", MessageImportance.High); + return false; + } + + if (string.IsNullOrWhiteSpace(DestinationDirectory)) { + Log.LogMessageFromText(nameof(DestinationDirectory) + " is an empty string", MessageImportance.High); + return false; + } + + var assembliesToFix = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var tmp in AssembliesToMakePublic.Split(';')) { + var asmName = tmp.Trim(); + var asmSimpleName = asmName; + int index = asmSimpleName.IndexOf(','); + if (index >= 0) + asmSimpleName = asmSimpleName.Substring(0, index).Trim(); + if (asmSimpleName.Length == 0) + continue; + assembliesToFix.Add(asmSimpleName); + } + + OutputReferencePath = new ITaskItem[ReferencePath.Length]; + byte[]? ivtBlob = null; + for (int i = 0; i < ReferencePath.Length; i++) { + var file = ReferencePath[i]; + OutputReferencePath[i] = file; + var filename = file.ItemSpec; + var fileExt = Path.GetExtension(filename); + var asmSimpleName = Path.GetFileNameWithoutExtension(filename); + if (!assembliesToFix.Contains(asmSimpleName)) + continue; + if (!File.Exists(filename)) { + Log.LogMessageFromText($"File does not exist: {filename}", MessageImportance.High); + return false; + } + + var patchDir = DestinationDirectory; + Directory.CreateDirectory(patchDir); + + var fileInfo = new FileInfo(filename); + long filesize = fileInfo.Length; + long writeTime = fileInfo.LastWriteTimeUtc.ToBinary(); + + var extraInfo = $"_{VERSION} {filesize} {writeTime}_"; + var patchedFilename = Path.Combine(patchDir, asmSimpleName + extraInfo + fileExt); + if (StringComparer.OrdinalIgnoreCase.Equals(patchedFilename, filename)) + continue; + + if (!File.Exists(patchedFilename)) { + if (ivtBlob is null) + ivtBlob = CreateIVTBlob(IVTString); + var data = File.ReadAllBytes(filename); + try { + using (var peImage = new PEImage(data, filename, ImageLayout.File, verify: true)) { + using (var md = MetadataFactory.CreateMetadata(peImage, verify: true)) { + var result = new IVTPatcher(data, md, ivtBlob).Patch(); + if (result != IVTPatcherResult.OK) { + string errMsg; + switch (result) { + case IVTPatcherResult.NoCustomAttributes: + errMsg = $"Assembly '{asmSimpleName}' has no custom attributes"; + break; + case IVTPatcherResult.NoIVTs: + errMsg = $"Assembly '{asmSimpleName}' has no InternalsVisibleToAttributes"; + break; + case IVTPatcherResult.IVTBlobTooSmall: + errMsg = $"Assembly '{asmSimpleName}' has no InternalsVisibleToAttribute blob that is big enough to store '{IVTString}'. Use a shorter assembly name and/or a shorter public key, or skip PublicKey=xxxx... altogether (if it's a C# assembly)"; + break; + default: + Debug.Fail($"Unknown error result: {result}"); + errMsg = "Unknown error"; + break; + } + Log.LogMessageFromText(errMsg, MessageImportance.High); + return false; + } + try { + File.WriteAllBytes(patchedFilename, data); + } + catch { + try { File.Delete(patchedFilename); } catch { } + throw; + } + } + } + } + catch (Exception ex) when (ex is IOException || ex is BadImageFormatException) { + Log.LogMessageFromText($"File '{filename}' is not a .NET file", MessageImportance.High); + return false; + } + + var xmlDocFile = Path.ChangeExtension(filename, "xml"); + if (File.Exists(xmlDocFile)) { + var newXmlDocFile = Path.ChangeExtension(patchedFilename, "xml"); + if (File.Exists(newXmlDocFile)) + File.Delete(newXmlDocFile); + File.Copy(xmlDocFile, newXmlDocFile); + } + } + + OutputReferencePath[i] = new TaskItem(patchedFilename); + } + + return true; + } + + static byte[] CreateIVTBlob(string newIVTString) { + var caStream = new MemoryStream(); + var caWriter = new BinaryWriter(caStream); + caWriter.Write((ushort)1); + WriteString(caWriter, newIVTString); + caWriter.Write((ushort)0); + var newIVTBlob = caStream.ToArray(); + var compressedSize = GetCompressedUInt32Bytes((uint)newIVTBlob.Length); + var blob = new byte[compressedSize + newIVTBlob.Length]; + var blobStream = new MemoryStream(blob); + var blobWriter = new BinaryWriter(blobStream); + WriteCompressedUInt32(blobWriter, (uint)newIVTBlob.Length); + blobWriter.Write(newIVTBlob); + if (blobWriter.BaseStream.Position != blob.Length) + throw new InvalidOperationException(); + return blob; + } + + static void WriteString(BinaryWriter writer, string s) { + var bytes = Encoding.UTF8.GetBytes(s); + WriteCompressedUInt32(writer, (uint)bytes.Length); + writer.Write(bytes); + } + + static void WriteCompressedUInt32(BinaryWriter writer, uint value) { + if (value <= 0x7F) + writer.Write((byte)value); + else if (value <= 0x3FFF) { + writer.Write((byte)((value >> 8) | 0x80)); + writer.Write((byte)value); + } + else if (value <= 0x1FFFFFFF) { + writer.Write((byte)((value >> 24) | 0xC0)); + writer.Write((byte)(value >> 16)); + writer.Write((byte)(value >> 8)); + writer.Write((byte)value); + } + else + throw new ArgumentOutOfRangeException("UInt32 value can't be compressed"); + } + + static uint GetCompressedUInt32Bytes(uint value) { + if (value <= 0x7F) + return 1; + if (value <= 0x3FFF) + return 2; + else if (value <= 0x1FFFFFFF) + return 4; + throw new ArgumentOutOfRangeException("UInt32 value can't be compressed"); + } + } +} diff --git a/Build/MakeEverythingPublic/MakeEverythingPublic.csproj b/Build/MakeEverythingPublic/MakeEverythingPublic.csproj new file mode 100644 index 0000000..1393973 --- /dev/null +++ b/Build/MakeEverythingPublic/MakeEverythingPublic.csproj @@ -0,0 +1,25 @@ + + + + + + + netstandard2.0;$(TargetFrameworks) + $(DnSpyAssemblyCopyright) + $(DnSpyAssemblyVersion) + $(DnSpyAssemblyInformationalVersion) + + True + ..\..\dnSpy.snk + true + enable + + + + + + + + + diff --git a/Build/MakeEverythingPublic/MakeEverythingPublic.tasks b/Build/MakeEverythingPublic/MakeEverythingPublic.tasks new file mode 100644 index 0000000..ab084b1 --- /dev/null +++ b/Build/MakeEverythingPublic/MakeEverythingPublic.tasks @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/Build/compiled/ConvertToNetstandardReferences.dll b/Build/compiled/ConvertToNetstandardReferences.dll new file mode 100644 index 0000000..bf524d0 Binary files /dev/null and b/Build/compiled/ConvertToNetstandardReferences.dll differ diff --git a/Build/compiled/MakeEverythingPublic.dll b/Build/compiled/MakeEverythingPublic.dll new file mode 100644 index 0000000..478aef4 Binary files /dev/null and b/Build/compiled/MakeEverythingPublic.dll differ diff --git a/Build/compiled/dnlib.dll b/Build/compiled/dnlib.dll new file mode 100644 index 0000000..17c4185 Binary files /dev/null and b/Build/compiled/dnlib.dll differ diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..8c119d5 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,2 @@ + + diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 0000000..7367bb6 --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DnSpyCommon.props b/DnSpyCommon.props new file mode 100644 index 0000000..e7c7bcd --- /dev/null +++ b/DnSpyCommon.props @@ -0,0 +1,60 @@ + + + + + net48;net5.0-windows + false + false + false + true + false + true + strict;nullablePublicOnly + latest + latest + en + false + + true + + win-x86;win-x64 + cs;de;es;es-ES;fa;fr;hu;it;pt-BR;pt-PT;ru;tr;uk;zh-CN + + + 6.1.8.0 + + v6.1.8 + Copyright (C) 2014-2020 de4dot@gmail.com + + + NU1701;AD0001 + $(NoWarn);CS8767 + + + 1.7.0 + 3.3.2 + 1.9.0 + 16.7.0 + 1.1.142101 + 16.4.11 + 15.5.27130 + 15.5.27130 + 12.0.3 + 3.0.1 + 2.10.0 + 4.6.0 + + + + + + + false + true + $(DefineConstants);HAS_COMREFERENCE + + + diff --git a/Extensions/Examples/Example1.Extension/CodeCtxMenus.cs b/Extensions/Examples/Example1.Extension/CodeCtxMenus.cs new file mode 100644 index 0000000..45a1c6f --- /dev/null +++ b/Extensions/Examples/Example1.Extension/CodeCtxMenus.cs @@ -0,0 +1,155 @@ +using System; +using System.ComponentModel.Composition; +using System.Runtime.InteropServices; +using System.Windows; +using System.Windows.Input; +using dnlib.DotNet; +using dnSpy.Contracts.Controls; +using dnSpy.Contracts.Documents.Tabs.DocViewer; +using dnSpy.Contracts.Extension; +using dnSpy.Contracts.Menus; +using Microsoft.VisualStudio.Text; + +// Adds menu items to the text editor context menu +// If you have many similar commands, it's better to create a base class and derive from +// MenuItemBase instead of MenuItemBase, see TreeViewCtxMenus.cs for an example. + +namespace Example1.Extension { + static class Constants { + //TODO: Use your own guids + // The first number is the order of the group, and the guid is the guid of the group, + // see eg. dnSpy.Contracts.Menus.MenuConstants.GROUP_CTX_CODE_HEX etc + public const string GROUP_TEXTEDITOR = "20000,3567EC95-E68E-44CE-932C-98A686FDCACF"; + public const string GROUP_TREEVIEW = "20000,77ACC18E-D8EB-483B-8D93-3581574B8891"; + } + + // This gets loaded by dnSpy and is used to add the Ctrl+Alt+Q command + [ExportAutoLoaded] + sealed class CommandLoader : IAutoLoaded { + static readonly RoutedCommand Option1Command = new RoutedCommand("Option1Command", typeof(CommandLoader)); + + [ImportingConstructor] + CommandLoader(IWpfCommandService wpfCommandService, MySettings mySettings) { + var cmds = wpfCommandService.GetCommands(ControlConstants.GUID_DOCUMENTVIEWER_UICONTEXT); + // This command will be added to all text editors + cmds.Add(Option1Command, + (s, e) => mySettings.BoolOption1 = !mySettings.BoolOption1, + (s, e) => e.CanExecute = true, + ModifierKeys.Control | ModifierKeys.Alt, Key.Q); + } + } + + [ExportMenuItem(Header = "Option 1", InputGestureText = "Ctrl+Alt+Q", Group = Constants.GROUP_TEXTEDITOR, Order = 0)] + sealed class TextEditorCommand1 : MenuItemBase { + readonly MySettings mySettings; + + [ImportingConstructor] + TextEditorCommand1(MySettings mySettings) => this.mySettings = mySettings; + + public override bool IsChecked(IMenuItemContext context) => mySettings.BoolOption1; + public override void Execute(IMenuItemContext context) => mySettings.BoolOption1 = !mySettings.BoolOption1; + + // Only show this in the document viewer + public override bool IsVisible(IMenuItemContext context) => context.CreatorObject.Guid == new Guid(MenuConstants.GUIDOBJ_DOCUMENTVIEWERCONTROL_GUID); + } + + [ExportMenuItem(Header = "Option 2", Group = Constants.GROUP_TEXTEDITOR, Order = 10)] + sealed class TextEditorCommand2 : MenuItemBase { + readonly MySettings mySettings; + + [ImportingConstructor] + TextEditorCommand2(MySettings mySettings) => this.mySettings = mySettings; + + public override bool IsChecked(IMenuItemContext context) => mySettings.BoolOption2; + public override void Execute(IMenuItemContext context) => mySettings.BoolOption2 = !mySettings.BoolOption2; + + // Only show this in the document viewer + public override bool IsVisible(IMenuItemContext context) => context.CreatorObject.Guid == new Guid(MenuConstants.GUIDOBJ_DOCUMENTVIEWERCONTROL_GUID); + } + + [ExportMenuItem(Group = Constants.GROUP_TEXTEDITOR, Order = 20)] + sealed class TextEditorCommand3 : MenuItemBase { + public override void Execute(IMenuItemContext context) { + var md = GetTokenObj(context); + if (md is not null) { + try { + Clipboard.SetText($"{md.MDToken.Raw:X8}"); + } + catch (ExternalException) { } + } + } + + public override string? GetHeader(IMenuItemContext context) { + var md = GetTokenObj(context); + if (md is null) + return "Copy token"; + return $"Copy token {md.MDToken.Raw:X8}"; + } + + IMDTokenProvider? GetTokenObj(IMenuItemContext context) { + // Only show this in the document viewer + if (context.CreatorObject.Guid != new Guid(MenuConstants.GUIDOBJ_DOCUMENTVIEWERCONTROL_GUID)) + return null; + + // All references in the text editor are stored in TextReferences + var textRef = context.Find(); + if (textRef is null) + return null; + + return textRef.Reference as IMDTokenProvider; + } + + // Only show this in the document viewer + public override bool IsVisible(IMenuItemContext context) => context.CreatorObject.Guid == new Guid(MenuConstants.GUIDOBJ_DOCUMENTVIEWERCONTROL_GUID); + public override bool IsEnabled(IMenuItemContext context) => GetTokenObj(context) is not null; + } + + [ExportMenuItem(Group = Constants.GROUP_TEXTEDITOR, Order = 30)] + sealed class TextEditorCommand4 : MenuItemBase { + public override void Execute(IMenuItemContext context) { + var documentViewer = GetDocumentViewer(context); + if (documentViewer is not null) { + try { + var lineColumn = GetLineColumn(documentViewer.Caret.Position.VirtualBufferPosition); + Clipboard.SetText($"Line,col: {lineColumn.Line + 1},{lineColumn.Column + 1}"); + } + catch (ExternalException) { } + } + } + + public override string? GetHeader(IMenuItemContext context) { + var documentViewer = GetDocumentViewer(context); + if (documentViewer is null) + return "Copy line and column"; + var lineColumn = GetLineColumn(documentViewer.Caret.Position.VirtualBufferPosition); + return $"Copy line,col {lineColumn.Line + 1},{lineColumn.Column + 1}"; + } + + LineColumn GetLineColumn(VirtualSnapshotPoint point) { + var line = point.Position.GetContainingLine(); + int column = point.Position - line.Start + point.VirtualSpaces; + return new LineColumn(line.LineNumber, column); + } + + struct LineColumn { + public int Line { get; } + public int Column { get; } + public LineColumn(int line, int column) { + Line = line; + Column = column; + } + } + + IDocumentViewer? GetDocumentViewer(IMenuItemContext context) { + // Only show this in the document viewer + if (context.CreatorObject.Guid != new Guid(MenuConstants.GUIDOBJ_DOCUMENTVIEWERCONTROL_GUID)) + return null; + + return context.Find(); + } + + // Only show this in the document viewer + public override bool IsVisible(IMenuItemContext context) => context.CreatorObject.Guid == new Guid(MenuConstants.GUIDOBJ_DOCUMENTVIEWERCONTROL_GUID); + public override bool IsEnabled(IMenuItemContext context) => GetDocumentViewer(context) is not null; + } +} diff --git a/Extensions/Examples/Example1.Extension/Example1.Extension.csproj b/Extensions/Examples/Example1.Extension/Example1.Extension.csproj new file mode 100644 index 0000000..baddcf1 --- /dev/null +++ b/Extensions/Examples/Example1.Extension/Example1.Extension.csproj @@ -0,0 +1,20 @@ + + + + + + Example1.Extension.x + enable + true + + + + + + + + + + + + diff --git a/Extensions/Examples/Example1.Extension/MainMenuCommands.cs b/Extensions/Examples/Example1.Extension/MainMenuCommands.cs new file mode 100644 index 0000000..3c4856c --- /dev/null +++ b/Extensions/Examples/Example1.Extension/MainMenuCommands.cs @@ -0,0 +1,49 @@ +using dnSpy.Contracts.App; +using dnSpy.Contracts.Menus; + +// Adds a new "_Extension" menu and several commands. +// Adds a command to the View menu + +namespace Example1.Extension { + static class MainMenuConstants { + //TODO: Use your own guids + public const string APP_MENU_EXTENSION = "4E6829A6-AEA0-4803-9344-D19BF0A74DA1"; + public const string GROUP_EXTENSION_MENU1 = "0,73BEBC37-387A-4004-8076-A1A90A17611B"; + public const string GROUP_EXTENSION_MENU2 = "10,C21B8B99-A2E4-474F-B4BC-4CF348ECBD0A"; + } + + // Create the Extension menu and place it right after the Debug menu + [ExportMenu(OwnerGuid = MenuConstants.APP_MENU_GUID, Guid = MainMenuConstants.APP_MENU_EXTENSION, Order = MenuConstants.ORDER_APP_MENU_DEBUG + 0.1, Header = "_Extension")] + sealed class DebugMenu : IMenu { + } + + [ExportMenuItem(OwnerGuid = MainMenuConstants.APP_MENU_EXTENSION, Header = "Command #1", Group = MainMenuConstants.GROUP_EXTENSION_MENU1, Order = 0)] + sealed class ExtensionCommand1 : MenuItemBase { + public override void Execute(IMenuItemContext context) => MsgBox.Instance.Show("Command #1"); + } + + [ExportMenuItem(OwnerGuid = MainMenuConstants.APP_MENU_EXTENSION, Header = "Command #2", Group = MainMenuConstants.GROUP_EXTENSION_MENU1, Order = 10)] + sealed class ExtensionCommand2 : MenuItemBase { + public override void Execute(IMenuItemContext context) => MsgBox.Instance.Show("Command #2"); + } + + [ExportMenuItem(OwnerGuid = MainMenuConstants.APP_MENU_EXTENSION, Header = "Command #3", Group = MainMenuConstants.GROUP_EXTENSION_MENU2, Order = 0)] + sealed class ExtensionCommand3 : MenuItemBase { + public override void Execute(IMenuItemContext context) => MsgBox.Instance.Show("Command #3"); + } + + [ExportMenuItem(OwnerGuid = MainMenuConstants.APP_MENU_EXTENSION, Header = "Command #4", Group = MainMenuConstants.GROUP_EXTENSION_MENU2, Order = 10)] + sealed class ExtensionCommand4 : MenuItemBase { + public override void Execute(IMenuItemContext context) => MsgBox.Instance.Show("Command #4"); + } + + [ExportMenuItem(OwnerGuid = MainMenuConstants.APP_MENU_EXTENSION, Header = "Command #5", Group = MainMenuConstants.GROUP_EXTENSION_MENU2, Order = 20)] + sealed class ExtensionCommand5 : MenuItemBase { + public override void Execute(IMenuItemContext context) => MsgBox.Instance.Show("Command #5"); + } + + [ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_VIEW_GUID, Header = "Command #1", Group = MenuConstants.GROUP_APP_MENU_VIEW_WINDOWS, Order = 1000)] + sealed class ViewCommand1 : MenuItemBase { + public override void Execute(IMenuItemContext context) => MsgBox.Instance.Show("View Command #1"); + } +} diff --git a/Extensions/Examples/Example1.Extension/MySettings.cs b/Extensions/Examples/Example1.Extension/MySettings.cs new file mode 100644 index 0000000..6f16830 --- /dev/null +++ b/Extensions/Examples/Example1.Extension/MySettings.cs @@ -0,0 +1,85 @@ +using System; +using System.ComponentModel; +using System.ComponentModel.Composition; +using dnSpy.Contracts.MVVM; +using dnSpy.Contracts.Settings; + +// Reads and writes the extension settings + +namespace Example1.Extension { + class MySettings : ViewModelBase { + public bool BoolOption1 { + get => boolOption1; + set { + if (boolOption1 != value) { + boolOption1 = value; + OnPropertyChanged(nameof(BoolOption1)); + } + } + } + bool boolOption1 = true; + + public bool BoolOption2 { + get => boolOption2; + set { + if (boolOption2 != value) { + boolOption2 = value; + OnPropertyChanged(nameof(BoolOption2)); + } + } + } + bool boolOption2 = false; + + public string StringOption3 { + get => stringOption3; + set { + if (stringOption3 != value) { + stringOption3 = value; + OnPropertyChanged(nameof(StringOption3)); + } + } + } + string stringOption3 = string.Empty; + + public MySettings Clone() => CopyTo(new MySettings()); + + public MySettings CopyTo(MySettings other) { + other.BoolOption1 = BoolOption1; + other.BoolOption2 = BoolOption2; + other.StringOption3 = StringOption3; + return other; + } + } + + // Export this class so it can be imported by other classes in this extension + [Export(typeof(MySettings))] + sealed class MySettingsImpl : MySettings { + //TODO: Use your own guid + static readonly Guid SETTINGS_GUID = new Guid("A308405D-0DF5-4C56-8B1E-8CE7BA6365E1"); + + readonly ISettingsService settingsService; + + // Tell MEF to pass in the required ISettingsService instance exported by dnSpy + [ImportingConstructor] + MySettingsImpl(ISettingsService settingsService) { + this.settingsService = settingsService; + + // Read the settings from the file or use the default values if our settings haven't + // been saved to it yet. + + var sect = settingsService.GetOrCreateSection(SETTINGS_GUID); + BoolOption1 = sect.Attribute(nameof(BoolOption1)) ?? BoolOption1; + BoolOption2 = sect.Attribute(nameof(BoolOption2)) ?? BoolOption2; + StringOption3 = sect.Attribute(nameof(StringOption3)) ?? StringOption3; + PropertyChanged += MySettingsImpl_PropertyChanged; + } + + void MySettingsImpl_PropertyChanged(object? sender, PropertyChangedEventArgs e) { + // Save the settings + var sect = settingsService.RecreateSection(SETTINGS_GUID); + sect.Attribute(nameof(BoolOption1), BoolOption1); + sect.Attribute(nameof(BoolOption2), BoolOption2); + sect.Attribute(nameof(StringOption3), StringOption3); + } + } +} diff --git a/Extensions/Examples/Example1.Extension/MySettingsControl.xaml b/Extensions/Examples/Example1.Extension/MySettingsControl.xaml new file mode 100644 index 0000000..3dc1e6e --- /dev/null +++ b/Extensions/Examples/Example1.Extension/MySettingsControl.xaml @@ -0,0 +1,8 @@ + + + + + + diff --git a/Extensions/Examples/Example1.Extension/MySettingsControl.xaml.cs b/Extensions/Examples/Example1.Extension/MySettingsControl.xaml.cs new file mode 100644 index 0000000..b3915fd --- /dev/null +++ b/Extensions/Examples/Example1.Extension/MySettingsControl.xaml.cs @@ -0,0 +1,7 @@ +using System.Windows.Controls; + +namespace Example1.Extension { + public partial class MySettingsControl : UserControl { + public MySettingsControl() => InitializeComponent(); + } +} diff --git a/Extensions/Examples/Example1.Extension/MySettingsPage.cs b/Extensions/Examples/Example1.Extension/MySettingsPage.cs new file mode 100644 index 0000000..4f6856a --- /dev/null +++ b/Extensions/Examples/Example1.Extension/MySettingsPage.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using dnSpy.Contracts.Images; +using dnSpy.Contracts.Settings.Dialog; + +// Adds an options dialog box page showing settings saved in MySettings + +namespace Example1.Extension { + // This instance gets called by dnSpy to create the page each time the user opens the options dialog + [Export(typeof(IAppSettingsPageProvider))] // Tell MEF we're exporting this instance + sealed class MyAppSettingsPageProvider : IAppSettingsPageProvider { + readonly MySettings mySettings; + + // This constructor gets the single MySettingsImpl instance exported by MySettingsImpl in MySettings.cs + [ImportingConstructor] + MyAppSettingsPageProvider(MySettings mySettings) => this.mySettings = mySettings; + + public IEnumerable Create() { + // We only create one page + yield return new MyAppSettingsPage(mySettings); + } + } + + sealed class MyAppSettingsPage : AppSettingsPage { + //TODO: Use your own GUID + static readonly Guid THE_GUID = new Guid("AE905210-A789-4AE2-B83B-537515D9F435"); + + // Guid of parent page or Guid.Empty if it has none + public override Guid ParentGuid => Guid.Empty; + + // Unique guid of this settings page + public override Guid Guid => THE_GUID; + + // The order of the page, let's place it after the debugger page + public override double Order => AppSettingsConstants.ORDER_DEBUGGER + 0.1; + + public override string Title => "MySettings"; + + // An image that can be shown. You can return ImageReference.None if you don't want an image. + // Let's return an image since no other settings page is currently using images. + public override ImageReference Icon => DsImages.Assembly; + + // This is the content shown in the page. It should be a WPF object (eg. a UserControl) or a + // ViewModel with a DataTemplate defined in a resource dictionary. + public override object? UIObject { + get { + if (uiObject is null) { + uiObject = new MySettingsControl(); + uiObject.DataContext = newSettings; + } + return uiObject; + } + } + MySettingsControl? uiObject; + + readonly MySettings globalSettings; + readonly MySettings newSettings; + + public MyAppSettingsPage(MySettings mySettings) { + globalSettings = mySettings; + newSettings = mySettings.Clone(); + } + + public override void OnApply() => + // OK/Apply was pressed, save the settings + newSettings.CopyTo(globalSettings); + + public override void OnClosed() { + // The dialog box was closed + } + } +} diff --git a/Extensions/Examples/Example1.Extension/README.md b/Extensions/Examples/Example1.Extension/README.md new file mode 100644 index 0000000..5d84a28 --- /dev/null +++ b/Extensions/Examples/Example1.Extension/README.md @@ -0,0 +1,9 @@ + +This extension shows how to do the basic things. It: + +- Reads and writes settings (MySettings.cs) +- Adds a page to the options dialog box showing some of the options (MySettingsPage.cs) +- Adds options to the text editor's context menu and a Ctrl+Alt+Q keyboard shortcut (CodeCtxMenus.cs) +- Adds treeview context menus (TreeViewCtxMenus.cs) +- Adds a "_Extension" menu and menu items and a View menu command (MainMenuCommands.cs) +- Adds a button and a combobox to the toolbar (ToolBarCommands.cs) diff --git a/Extensions/Examples/Example1.Extension/TheExtension.cs b/Extensions/Examples/Example1.Extension/TheExtension.cs new file mode 100644 index 0000000..7290bc9 --- /dev/null +++ b/Extensions/Examples/Example1.Extension/TheExtension.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using dnSpy.Contracts.Extension; + +// Each extension should export one class implementing IExtension + +namespace Example1.Extension { + [ExportExtension] + sealed class TheExtension : IExtension { + public IEnumerable MergedResourceDictionaries { + get { + // We don't have any extra resource dictionaries + yield break; + } + } + + public ExtensionInfo ExtensionInfo => new ExtensionInfo { + ShortDescription = "Example1 extension", + }; + + public void OnEvent(ExtensionEvent @event, object? obj) { + // We don't care about any events + } + } +} diff --git a/Extensions/Examples/Example1.Extension/ToolBarCommands.cs b/Extensions/Examples/Example1.Extension/ToolBarCommands.cs new file mode 100644 index 0000000..436e238 --- /dev/null +++ b/Extensions/Examples/Example1.Extension/ToolBarCommands.cs @@ -0,0 +1,39 @@ +using System.Windows; +using System.Windows.Controls; +using dnSpy.Contracts.App; +using dnSpy.Contracts.Images; +using dnSpy.Contracts.ToolBars; + +// Adds a toolbar button and combobox between the asm editor and debugger toolbar items + +namespace Example1.Extension { + static class TBConstants { + //TODO: Use your own guid + // Place it between the asm editor and debugger, see dnSpy.Contracts.ToolBars.ToolBarConstants: + // GROUP_APP_TB_MAIN_ASMED_UNDO = "4000,6351DBFC-6D8D-4847-B3F2-BC376912B9C2" + // GROUP_APP_TB_MAIN_DEBUG = "5000,A0AFBC69-B6D1-46FE-96C8-EC380DEBE9AA" + public const string GROUP_APP_TB_EXTENSION = "4500,AF461C50-6E91-41B8-9771-0BAE9B77BC69"; + } + + [ExportToolBarButton(Icon = DsImagesAttribute.Assembly, ToolTip = "Click Me", Group = TBConstants.GROUP_APP_TB_EXTENSION, Order = 0)] + sealed class TBCommand1 : ToolBarButtonBase { + public override void Execute(IToolBarItemContext context) => MsgBox.Instance.Show("Command #1"); + } + + [ExportToolBarObject(Group = TBConstants.GROUP_APP_TB_EXTENSION, Order = 10)] + sealed class TBCommand2 : ToolBarObjectBase { + readonly ComboBox comboBox; + + TBCommand2() { + comboBox = new ComboBox(); + comboBox.Width = 100; + comboBox.Items.Add("Item #1"); + comboBox.Items.Add("Item #2"); + comboBox.Items.Add("Item #3"); + comboBox.Items.Add("Item #4"); + comboBox.SelectedIndex = 1; + } + + public override object GetUIObject(IToolBarItemContext context, IInputElement? commandTarget) => comboBox; + } +} diff --git a/Extensions/Examples/Example1.Extension/TreeViewCtxMenus.cs b/Extensions/Examples/Example1.Extension/TreeViewCtxMenus.cs new file mode 100644 index 0000000..bbb787d --- /dev/null +++ b/Extensions/Examples/Example1.Extension/TreeViewCtxMenus.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Windows; +using dnlib.DotNet.Emit; +using dnSpy.Contracts.App; +using dnSpy.Contracts.Documents.TreeView; +using dnSpy.Contracts.Menus; +using dnSpy.Contracts.TreeView; + +// Adds a couple of commands to the file treeview context menu. +// Since there are several commands using the same state, MenuItemBase is used +// as the base class so the context is created once and shared by all commands. + +namespace Example1.Extension { + sealed class TVContext { + public bool SomeValue { get; } + public DocumentTreeNodeData[] Nodes { get; } + + public TVContext(bool someValue, IEnumerable nodes) { + SomeValue = someValue; + Nodes = nodes.ToArray(); + } + } + + abstract class TVCtxMenuCommand : MenuItemBase { + protected sealed override object CachedContextKey => ContextKey; + static readonly object ContextKey = new object(); + + protected sealed override TVContext? CreateContext(IMenuItemContext context) { + // Make sure it's the file treeview + if (context.CreatorObject.Guid != new Guid(MenuConstants.GUIDOBJ_DOCUMENTS_TREEVIEW_GUID)) + return null; + + // Extract the data needed by the context + var nodes = context.Find(); + if (nodes is null) + return null; + var newNodes = nodes.OfType(); + + bool someValue = true; + return new TVContext(someValue, newNodes); + } + } + + [ExportMenuItem(Header = "Command #1", Group = Constants.GROUP_TREEVIEW, Order = 0)] + sealed class TVCommand1 : TVCtxMenuCommand { + public override void Execute(TVContext context) => MsgBox.Instance.Show("Command #1"); + public override bool IsEnabled(TVContext context) => context.Nodes.Length > 1; + } + + [ExportMenuItem(Header = "Command #2", Group = Constants.GROUP_TREEVIEW, Order = 10)] + sealed class TVCommand2 : TVCtxMenuCommand { + public override void Execute(TVContext context) => MsgBox.Instance.Show("Command #2"); + public override bool IsVisible(TVContext context) => context.Nodes.Length > 0; + } + + [ExportMenuItem(Header = "Command #3", Group = Constants.GROUP_TREEVIEW, Order = 20)] + sealed class TVCommand3 : TVCtxMenuCommand { + public override void Execute(TVContext context) { + int secretNum = new Random().Next() % 10; + MsgBox.Instance.Ask("Number", null, "Guess a number", null, s => { + if (string.IsNullOrWhiteSpace(s)) + return "Enter a number"; + if (!int.TryParse(s, out int num)) + return "Not an integer"; + if (num == 42) + return "Nope!"; + if (num != secretNum) + return "WRONG!!!"; + return string.Empty; + }); + } + } + + [ExportMenuItem(Header = "Command #4", Group = Constants.GROUP_TREEVIEW, Order = 30)] + sealed class TVCommand4 : TVCtxMenuCommand { + public override void Execute(TVContext context) => MsgBox.Instance.Show("Command #4"); + public override bool IsEnabled(TVContext context) => context.Nodes.Length == 1 && context.Nodes[0] is ModuleDocumentNode; + } + + [ExportMenuItem(Group = Constants.GROUP_TREEVIEW, Order = 40)] + sealed class TVCommand5 : TVCtxMenuCommand { + public override void Execute(TVContext context) { + var node = GetTokenNode(context); + if (node is not null) { + try { + Clipboard.SetText($"{node.Reference!.MDToken.Raw:X8}"); + } + catch (ExternalException) { } + } + } + + IMDTokenNode? GetTokenNode(TVContext context) { + if (context.Nodes.Length == 0) + return null; + return context.Nodes[0] as IMDTokenNode; + } + + public override string? GetHeader(TVContext context) { + var node = GetTokenNode(context); + if (node is null) + return string.Empty; + return $"Copy token {node.Reference!.MDToken.Raw:X8}"; + } + + public override bool IsVisible(TVContext context) => GetTokenNode(context) is not null; + } + + [ExportMenuItem(Header = "Copy Second Instruction", Group = Constants.GROUP_TREEVIEW, Order = 50)] + sealed class TVCommand6 : TVCtxMenuCommand { + public override void Execute(TVContext context) { + var instr = GetSecondInstruction(context); + if (instr is not null) { + try { + Clipboard.SetText($"Second instruction: {instr}"); + } + catch (ExternalException) { } + } + } + + Instruction? GetSecondInstruction(TVContext context) { + if (context.Nodes.Length == 0) + return null; + var methNode = context.Nodes[0] as MethodNode; + if (methNode is null) + return null; + var body = methNode.MethodDef.Body; + if (body is null || body.Instructions.Count < 2) + return null; + return body.Instructions[1]; + } + + public override bool IsEnabled(TVContext context) => GetSecondInstruction(context) is not null; + } +} diff --git a/Extensions/Examples/Example2.Extension/AssemblyChildNodeTabContent.cs b/Extensions/Examples/Example2.Extension/AssemblyChildNodeTabContent.cs new file mode 100644 index 0000000..c72c7bf --- /dev/null +++ b/Extensions/Examples/Example2.Extension/AssemblyChildNodeTabContent.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using System.Windows; +using System.Windows.Controls; +using dnSpy.Contracts.Documents.Tabs; +using dnSpy.Contracts.Documents.TreeView; +using dnSpy.Contracts.MVVM; +using dnSpy.Contracts.Settings; + +// This file adds custom document tab content when the user clicks on our new AssemblyChildNode tree node. +// This node is created by TreeNodeDataProvider.cs. + +namespace Example2.Extension { + [ExportDocumentTabContentFactory] + sealed class AssemblyChildNodeTabContentFactory : IDocumentTabContentFactory { + // Called to create a new IFileTabContent. If it's our new tree node, create a new IFileTabContent for it + public DocumentTabContent? Create(IDocumentTabContentFactoryContext context) { + if (context.Nodes.Length == 1 && context.Nodes[0] is AssemblyChildNode) + return new AssemblyChildNodeTabContent((AssemblyChildNode)context.Nodes[0]); + return null; + } + + //TODO: Use your own guid + static readonly Guid GUID_SerializedContent = new Guid("FC6D2EC8-6FF8-4071-928E-EB07735A6402"); + + public DocumentTabContent? Deserialize(Guid guid, ISettingsSection section, IDocumentTabContentFactoryContext context) { + if (guid == GUID_SerializedContent) { + // Serialize() doesn't add anything extra to 'section', but if it did, you'd have to + // get that info here and return null if the serialized data wasn't found. + var node = context.Nodes.Length == 1 ? context.Nodes[0] as AssemblyChildNode : null; + if (node is not null) + return new AssemblyChildNodeTabContent(node); + } + return null; + } + + public Guid? Serialize(DocumentTabContent content, ISettingsSection section) { + if (content is AssemblyChildNodeTabContent) { + // There's nothing else we need to serialize it, but if there were, use 'section' + // to write the info needed by Deserialize() above. + return GUID_SerializedContent; + } + return null; + } + } + + sealed class AssemblyChildNodeTabContent : DocumentTabContent { + // Returns all nodes used to generate the content + public override IEnumerable Nodes { + get { yield return node; } + } + + public override string Title => node.ToString(DocumentNodeWriteOptions.Title); + public override object? ToolTip => node.ToString(DocumentNodeWriteOptions.Title | DocumentNodeWriteOptions.ToolTip); + + readonly AssemblyChildNode node; + + public AssemblyChildNodeTabContent(AssemblyChildNode node) => this.node = node; + + // Called when the user opens a new tab. Override CanClone and return false if + // Clone() isn't supported + public override DocumentTabContent Clone() => new AssemblyChildNodeTabContent(node); + + // Gets called to create the UI context. It can be shared by any IFileTabContent in this tab. + // Eg. there's only one text editor per tab, shared by all IFileTabContents that need a text + // editor. + public override DocumentTabUIContext CreateUIContext(IDocumentTabUIContextLocator locator) { + // This custom view object is shared by all nodes of the same type. If we didn't want it + // to be shared, we could use 'node' or 'this' as the key. + var key = node.GetType(); + // var key = node; // uncomment to not share it + + // If the UI object has already been created, use it, else create it. The object is + // stored in a weak reference unless you use the other method override. + return locator.Get(key, () => new AssemblyChildNodeUIContext()); + } + + public override void OnShow(IShowContext ctx) { + // Get the real type, created by CreateUIContext() above. + var uiCtx = (AssemblyChildNodeUIContext)ctx.UIContext; + + // You could initialize some stuff, eg. update its DataContext or whatever + uiCtx.Initialize("some input"); // pretend we need to initialize something + } + } + + sealed class AssemblyChildNodeUIContext : DocumentTabUIContext { + // The element inside UIObject that gets the focus when the tool window should be focused. + // If it's not as easy as calling FocusedElement.Focus() to focus it, you must implement + // dnSpy.Contracts.Controls.IFocusable. + public override IInputElement? FocusedElement => content; + + // The element in UIObject that gets the scale transform. null can be returned to disable scaling. + public override FrameworkElement? ZoomElement => content; + + // The UI object shown in the tab. Should be a WPF control (eg. UserControl) or a .NET object + // with a DataTemplate. + public override object? UIObject => content; + + readonly ContentPresenter content; + readonly AssemblyChildNodeVM vm; + + public AssemblyChildNodeUIContext() { + vm = new AssemblyChildNodeVM(); + // A ContentPresenter + DataTemplate is used to show the VM, but you could of course use + // a UserControl. + content = new ContentPresenter { + Focusable = true, + Content = vm, + }; + } + + sealed class MyUIState { + public string Value1; + public bool Value2; + + public MyUIState(string value1, bool value2) { + Value1 = value1; + Value2 = value2; + } + } + + // Optional: + // Called to create an object that can be passed to RestoreUIState() + public override object? DeserializeUIState(ISettingsSection section) { + var value1 = section.Attribute(nameof(MyUIState.Value1)); + var value2 = section.Attribute(nameof(MyUIState.Value2)); + if (value1 is null || value2 is null) + return null; + + return new MyUIState(value1, value2.Value); + } + + // Optional: + // Saves the object returned by CreateUIState() + public override void SerializeUIState(ISettingsSection section, object? obj) { + var d = obj as MyUIState; + if (d is null) + return; + + section.Attribute(nameof(d.Value1), d.Value1); + section.Attribute(nameof(d.Value2), d.Value2); + } + + // Optional: + // Creates the UI state or returns null. This is an example, so return some random data + public override object? CreateUIState() => new MyUIState("Some string", true); + + // Optional: + // Restores the UI state + public override void RestoreUIState(object? obj) { + var d = obj as MyUIState; + if (d is null) + return; + + // Here's where you'd restore the UI state, eg position etc. + } + + // Called by AssemblyChildNodeTabContent above to initialize it before it's shown again + public void Initialize(string s) { + // here we could initialize something before it's shown again, eg. initialize the DataContext + } + } + + sealed class AssemblyChildNodeVM : ViewModelBase { + public string SomeMessage { + get => someMessage; + set { + if (someMessage != value) { + someMessage = value; + OnPropertyChanged(nameof(SomeMessage)); + } + } + } + string someMessage = "Hello World"; + } +} diff --git a/Extensions/Examples/Example2.Extension/Colorizer.cs b/Extensions/Examples/Example2.Extension/Colorizer.cs new file mode 100644 index 0000000..9bd3bfd --- /dev/null +++ b/Extensions/Examples/Example2.Extension/Colorizer.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Windows.Media; +using dnSpy.Contracts.Text; +using dnSpy.Contracts.Text.Classification; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Text.Tagging; +using Microsoft.VisualStudio.Utilities; + +// Colorizes all text files. +// All "if" words use the Yellow color (default: no background, yellow foreground color) +// All 2 letter words use green background (foreground not changed) +// All 3 letter words use a white foreground and a red background +// All 4 letter words use the Error color (default: no background, red foreground color) + +namespace Example2.Extension { + // Define our classification types. A classification type is converted to a color + static class Constants { + // Use unique names + public const string Color1_ClassificationTypeName = "Example2.Extension.Color1"; + public const string Color2_ClassificationTypeName = "Example2.Extension.Color2"; + + // Disable compiler warnings. The fields aren't referenced, just exported so + // the metadata can be added to some table. The fields will always be null. +#pragma warning disable CS0169 + // Export the classes that define the name, and base types + [Export(typeof(ClassificationTypeDefinition))] + [Name(Color1_ClassificationTypeName)] + [BaseDefinition(PredefinedClassificationTypeNames.FormalLanguage)] + static ClassificationTypeDefinition? Color1ClassificationTypeDefinition; + + [Export(typeof(ClassificationTypeDefinition))] + [Name(Color2_ClassificationTypeName)] + [BaseDefinition(PredefinedClassificationTypeNames.FormalLanguage)] + static ClassificationTypeDefinition? Color2ClassificationTypeDefinition; +#pragma warning restore CS0169 + + // Export the classes that define the colors and order + [Export(typeof(EditorFormatDefinition))] + [ClassificationType(ClassificationTypeNames = Color1_ClassificationTypeName)] + [Name("My Color #1")] + [UserVisible(true)] + [Order(After = Priority.High)] + sealed class Color1ClassificationFormatDefinition : ClassificationFormatDefinition { + Color1ClassificationFormatDefinition() => BackgroundBrush = Brushes.Green; + } + + [Export(typeof(EditorFormatDefinition))] + [ClassificationType(ClassificationTypeNames = Color2_ClassificationTypeName)] + [Name("My Color #2")] + [UserVisible(true)] + [Order(After = Priority.High)] + sealed class Color2ClassificationFormatDefinition : ClassificationFormatDefinition { + Color2ClassificationFormatDefinition() { + ForegroundBrush = Brushes.White; + BackgroundBrush = Brushes.Red; + } + } + } + + // Export our tagger provider. Each time a new text editor is created with our supported + // content type (TEXT), this class gets called to create the tagger. + [Export(typeof(ITaggerProvider))] + [TagType(typeof(IClassificationTag))] + [ContentType(ContentTypes.Text)] + sealed class TextTaggerProvider : ITaggerProvider { + readonly IClassificationTypeRegistryService classificationTypeRegistryService; + + [ImportingConstructor] + TextTaggerProvider(IClassificationTypeRegistryService classificationTypeRegistryService) => this.classificationTypeRegistryService = classificationTypeRegistryService; + + public ITagger? CreateTagger(ITextBuffer buffer) where T : ITag => + // All text content types (including C#/VB code) derive from the TEXT content + // type, so our tagger will get called to colorize every text file that's shown + // in a text editor. + new TextTagger(classificationTypeRegistryService) as ITagger; + } + + // This class gets called to colorize supported files + sealed class TextTagger : ITagger { + // We don't raise it, so add empty add/remove methods to prevent compiler warnings. + // This event must be raised when you detect changes to spans in the document. If + // your GetTags() method does async work, you should raise it when the async work + // is completed. + public event EventHandler? TagsChanged { + add { } + remove { } + } + + readonly IClassificationType color1; + readonly IClassificationType color2; + readonly IClassificationType color3; + readonly IClassificationType color4; + + public TextTagger(IClassificationTypeRegistryService classificationTypeRegistryService) { + // Get the classification types we need + color1 = classificationTypeRegistryService.GetClassificationType(Constants.Color1_ClassificationTypeName); + color2 = classificationTypeRegistryService.GetClassificationType(Constants.Color2_ClassificationTypeName); + // Get some classification types created by some other code + color3 = classificationTypeRegistryService.GetClassificationType(ThemeClassificationTypeNames.Yellow); + color4 = classificationTypeRegistryService.GetClassificationType(ThemeClassificationTypeNames.Error); + } + + // Gets called to colorize a range of the file. It's typically called once per visible line + public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans) { + if (spans.Count == 0) + yield break; + // All spans have the same snapshot since it's a normalized collection (sorted, and no span intersects any other span) + var snapshot = spans[0].Snapshot; + foreach (var span in spans) { + foreach (var word in GetWords(span.GetText())) { + // Create a new span. word.Item2 is the offset within the string, so add span.Start to + // get the offset in the snapshot. + var wordSpan = new SnapshotSpan(snapshot, new Span(span.Span.Start + word.offset, word.word.Length)); + if (word.word == "if") + yield return new TagSpan(wordSpan, new ClassificationTag(color3)); + else if (word.word.Length == 2) + yield return new TagSpan(wordSpan, new ClassificationTag(color1)); + else if (word.word.Length == 3) + yield return new TagSpan(wordSpan, new ClassificationTag(color2)); + else if (word.word.Length == 4) + yield return new TagSpan(wordSpan, new ClassificationTag(color4)); + else { + // Ignore the rest + } + } + } + } + + IEnumerable<(string word, int offset)> GetWords(string s) { + int offset = 0; + for (;;) { + while (offset < s.Length && char.IsWhiteSpace(s[offset])) + offset++; + int wordOffset = offset; + while (offset < s.Length && !char.IsWhiteSpace(s[offset])) + offset++; + if (wordOffset == offset) + break; + yield return (s.Substring(wordOffset, offset - wordOffset), wordOffset); + } + } + } +} diff --git a/Extensions/Examples/Example2.Extension/DocumentViewerToolTipProvider.cs b/Extensions/Examples/Example2.Extension/DocumentViewerToolTipProvider.cs new file mode 100644 index 0000000..2e73be4 --- /dev/null +++ b/Extensions/Examples/Example2.Extension/DocumentViewerToolTipProvider.cs @@ -0,0 +1,26 @@ +using dnSpy.Contracts.Documents.Tabs.DocViewer.ToolTips; +using dnSpy.Contracts.Text; + +namespace Example2.Extension { + // This reference is added to the "decompiled" code by ModuleChildNode.Decompile() + sealed class StringInfoReference { + public string Message { get; } + + public StringInfoReference(string msg) => Message = msg; + } + + // Called by dnSpy to create a tooltip when hovering over a reference in the text editor + [ExportDocumentViewerToolTipProvider] + sealed class DocumentViewerToolTipProvider : IDocumentViewerToolTipProvider { + public object? Create(IDocumentViewerToolTipProviderContext context, object? @ref) { + // This reference is added to the "decompiled" code by ModuleChildNode.Decompile() + if (@ref is StringInfoReference sref) { + var provider = context.Create(); + provider.Output.Write(BoxedTextColor.String, sref.Message); + return provider.Create(); + } + + return null; + } + } +} diff --git a/Extensions/Examples/Example2.Extension/Example2.Extension.csproj b/Extensions/Examples/Example2.Extension/Example2.Extension.csproj new file mode 100644 index 0000000..416531b --- /dev/null +++ b/Extensions/Examples/Example2.Extension/Example2.Extension.csproj @@ -0,0 +1,20 @@ + + + + + + Example2.Extension.x + enable + true + + + + + + + + + + + + diff --git a/Extensions/Examples/Example2.Extension/HexColorizer.cs b/Extensions/Examples/Example2.Extension/HexColorizer.cs new file mode 100644 index 0000000..a35b0ea --- /dev/null +++ b/Extensions/Examples/Example2.Extension/HexColorizer.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Windows.Media; +using dnSpy.Contracts.Hex; +using dnSpy.Contracts.Hex.Tagging; +using dnSpy.Contracts.Text.Classification; +using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Utilities; + +namespace Example2.Extension { + // Define our classification types. A classification type is converted to a color + static class HexConstants { + // Use unique names + public const string HexColor1_ClassificationTypeName = "Example2.Extension.HexColor1"; + public const string HexColor2_ClassificationTypeName = "Example2.Extension.HexColor2"; + + // Disable compiler warnings. The fields aren't referenced, just exported so + // the metadata can be added to some table. The fields will always be null. +#pragma warning disable CS0169 + // Export the classes that define the name, and base types + [Export(typeof(ClassificationTypeDefinition))] + [Name(HexColor1_ClassificationTypeName)] + [BaseDefinition(PredefinedClassificationTypeNames.FormalLanguage)] + static ClassificationTypeDefinition? HexColor1ClassificationTypeDefinition; + + [Export(typeof(ClassificationTypeDefinition))] + [Name(HexColor2_ClassificationTypeName)] + [BaseDefinition(PredefinedClassificationTypeNames.FormalLanguage)] + static ClassificationTypeDefinition? HexColor2ClassificationTypeDefinition; +#pragma warning restore CS0169 + + // Export the classes that define the colors and order + [Export(typeof(EditorFormatDefinition))] + [ClassificationType(ClassificationTypeNames = HexColor1_ClassificationTypeName)] + [Name("My Hex Color #1")] + [UserVisible(true)] + [Order(After = Priority.High)] + sealed class Color1ClassificationFormatDefinition : ClassificationFormatDefinition { + Color1ClassificationFormatDefinition() => BackgroundBrush = Brushes.LightGray; + } + + [Export(typeof(EditorFormatDefinition))] + [ClassificationType(ClassificationTypeNames = HexColor2_ClassificationTypeName)] + [Name("My Hex Color #2")] + [UserVisible(true)] + [Order(After = Priority.High)] + sealed class Color2ClassificationFormatDefinition : ClassificationFormatDefinition { + Color2ClassificationFormatDefinition() { + ForegroundBrush = Brushes.White; + BackgroundBrush = Brushes.Red; + } + } + } + + // Export our tagger provider + [Export(typeof(HexTaggerProvider))] + [HexTagType(typeof(HexClassificationTag))] + sealed class HexTaggerProviderImpl : HexTaggerProvider { + readonly IClassificationTypeRegistryService classificationTypeRegistryService; + + [ImportingConstructor] + HexTaggerProviderImpl(IClassificationTypeRegistryService classificationTypeRegistryService) => this.classificationTypeRegistryService = classificationTypeRegistryService; + + public override IHexTagger? CreateTagger(HexBuffer buffer) => + new HexTaggerImpl(classificationTypeRegistryService) as IHexTagger; + } + + // This class gets called to colorize supported files + sealed class HexTaggerImpl : HexTagger { + // We don't raise it, so add empty add/remove methods to prevent compiler warnings. + // This event must be raised when you detect changes to spans in the document. If + // your GetTags() method does async work, you should raise it when the async work + // is completed. + public override event EventHandler? TagsChanged { + add { } + remove { } + } + + readonly IClassificationType color1; + readonly IClassificationType color2; + readonly IClassificationType color3; + readonly IClassificationType color4; + + public HexTaggerImpl(IClassificationTypeRegistryService classificationTypeRegistryService) { + // Get the classification types we need + color1 = classificationTypeRegistryService.GetClassificationType(HexConstants.HexColor1_ClassificationTypeName); + color2 = classificationTypeRegistryService.GetClassificationType(HexConstants.HexColor2_ClassificationTypeName); + // Get some classification types created by some other code + color3 = classificationTypeRegistryService.GetClassificationType(ThemeClassificationTypeNames.Yellow); + color4 = classificationTypeRegistryService.GetClassificationType(ThemeClassificationTypeNames.Error); + } + + // Called to classify a span of data, it can only classify bytes, not characters on a line + public override IEnumerable> GetTags(NormalizedHexBufferSpanCollection spans) { + if (spans.Count == 0) + yield break; + + var ourSpan = new HexBufferSpan(spans[0].Buffer, 10, 128); + foreach (var span in spans) { + if (!span.IntersectsWith(ourSpan)) + continue; + + // Classify both columns (values and ASCII) + yield return new HexTagSpan(ourSpan, HexSpanSelectionFlags.Selection, new HexClassificationTag(color2)); + } + } + + // Called to classify a view line, any character on the line can be classified + public override IEnumerable> GetTags(HexTaggerContext context) { + // Highlight every 16th line + if (context.Line.LineNumber.ToUInt64() % 16 == 0) + yield return new HexTextTagSpan(context.LineSpan, new HexClassificationTag(color1)); + + // If there are too many zeroes, change the color of the line + int zeroes = 0; + for (int i = 0; i < context.Line.HexBytes.Length; i++) { + if (context.Line.HexBytes.TryReadByte(i) == 0) + zeroes++; + } + if (zeroes >= context.Line.HexBytes.Length / 3) + yield return new HexTextTagSpan(context.LineSpan, new HexClassificationTag(color4)); + } + } +} diff --git a/Extensions/Examples/Example2.Extension/NewDsDocument.cs b/Extensions/Examples/Example2.Extension/NewDsDocument.cs new file mode 100644 index 0000000..ecb5ab7 --- /dev/null +++ b/Extensions/Examples/Example2.Extension/NewDsDocument.cs @@ -0,0 +1,122 @@ +using System; +using System.ComponentModel.Composition; +using System.IO; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Documents; +using dnSpy.Contracts.Documents.Tabs.DocViewer; +using dnSpy.Contracts.Documents.TreeView; +using dnSpy.Contracts.Images; +using dnSpy.Contracts.Text; + +// Adds a new IDsDocument that can be loaded into the document treeview. It gets its own DsDocumentNode. +// Open a .txt/.xml/.cs/.vb (see supportedExtensions) file to trigger this code. + +namespace Example2.Extension { + // All root nodes in the document treeview contain a IDsDocument instance. They don't need to be + // .NET files or even PE files, they can be any file or even non-file (eg. in-memory data). + sealed class MyDsDocument : DsDocument { + //TODO: Use your own guid + public static readonly Guid THE_GUID = new Guid("9058B02C-1FE0-4EC4-93D3-A378D4B6FCE1"); + + // We support serialization, so return a non-null value + public override DsDocumentInfo? SerializedDocument => new DsDocumentInfo(Filename, THE_GUID); + + // Since we open files from disk, we return a FilenameKey. + // If this gets changed, also update MyDsDocumentProvider.CreateKey() + public override IDsDocumentNameKey Key => new FilenameKey(Filename); + + // Used by MyDsDocumentNode.Decompile() to show the file in the text editor + public string Text { + get { + if (text is not null) + return text; + try { + return text = File.ReadAllText(Filename); + } + catch { + return text = $"Couldn't read the file: {Filename}"; + } + } + } + string? text; + + public static MyDsDocument? TryCreate(string filename) { + if (!File.Exists(filename)) + return null; + return new MyDsDocument(filename); + } + + MyDsDocument(string filename) => Filename = filename; + } + + // Gets called by the IDsDocumentService instance to create IDsDocument instances. If it's a .txt file + // or our MyDsDocument.THE_GUID, then create a MyDsDocument instance. + [Export(typeof(IDsDocumentProvider))] + sealed class MyDsDocumentProvider : IDsDocumentProvider { + public double Order => 0; + + public IDsDocument? Create(IDsDocumentService documentService, DsDocumentInfo documentInfo) { + if (documentInfo.Type == MyDsDocument.THE_GUID) + return MyDsDocument.TryCreate(documentInfo.Name); + // Also check for normal files + if (documentInfo.Type == DocumentConstants.DOCUMENTTYPE_FILE && IsSupportedFile(documentInfo.Name)) + return MyDsDocument.TryCreate(documentInfo.Name); + return null; + } + + public IDsDocumentNameKey? CreateKey(IDsDocumentService documentService, DsDocumentInfo documentInfo) { + if (documentInfo.Type == MyDsDocument.THE_GUID) + return new FilenameKey(documentInfo.Name); // Must match the key in MyDsDocument.Key + // Also check for normal files + if (documentInfo.Type == DocumentConstants.DOCUMENTTYPE_FILE && IsSupportedFile(documentInfo.Name)) + return new FilenameKey(documentInfo.Name); // Must match the key in MyDsDocument.Key + return null; + } + + static bool IsSupportedFile(string filename) { + foreach (var ext in supportedExtensions) { + if (filename.EndsWith(ext, StringComparison.OrdinalIgnoreCase)) + return true; + } + return false; + } + static readonly string[] supportedExtensions = new string[] { + ".txt", ".xml", ".cs", ".vb", + }; + } + + // Gets called by dnSpy to create a DsDocumentNode + [ExportDsDocumentNodeProvider] + sealed class MyDsDocumentNodeProvider : IDsDocumentNodeProvider { + public DsDocumentNode? Create(IDocumentTreeView documentTreeView, DsDocumentNode? owner, IDsDocument document) { + if (document is MyDsDocument myDocument) + return new MyDsDocumentNode(myDocument); + return null; + } + } + + // Our MyDsDocument tree node class. It implements IDecompileSelf to "decompile" itself. You could + // also export a IDecompileNode instance to do it, see TreeNodeDataProvider.cs for an example. + // Or you could create a completely new DocumentTabContent for these nodes, see AssemblyChildNodeTabContent.cs + sealed class MyDsDocumentNode : DsDocumentNode, IDecompileSelf { + //TODO: Use your own guid + public static readonly Guid THE_GUID = new Guid("4174A21D-D746-4658-9A44-DB8235EE5186"); + + readonly MyDsDocument document; + + public override Guid Guid => THE_GUID; + + public MyDsDocumentNode(MyDsDocument document) + : base(document) => this.document = document; + + protected override ImageReference GetIcon(IDotNetImageService dnImgMgr) => DsImages.TextFile; + protected override void WriteCore(ITextColorWriter output, IDecompiler decompiler, DocumentNodeWriteOptions options) => + output.WriteFilename(Path.GetFileName(document.Filename)); + + public bool Decompile(IDecompileNodeContext context) { + context.ContentTypeString = ContentTypes.PlainText; + context.Output.Write(document.Text, BoxedTextColor.Text); + return true; + } + } +} diff --git a/Extensions/Examples/Example2.Extension/OutputTextPane.cs b/Extensions/Examples/Example2.Extension/OutputTextPane.cs new file mode 100644 index 0000000..1488fd2 --- /dev/null +++ b/Extensions/Examples/Example2.Extension/OutputTextPane.cs @@ -0,0 +1,92 @@ +using System; +using System.ComponentModel.Composition; +using System.Diagnostics; +using dnSpy.Contracts.Extension; +using dnSpy.Contracts.Menus; +using dnSpy.Contracts.Output; +using dnSpy.Contracts.Text; + +// Creates an Output window text pane where our log messages will go. +// Adds context menu commands. + +namespace Example2.Extension { + // Holds an instance of our logger text pane + static class MyLogger { + //TODO: Use your own GUID + public static readonly Guid THE_GUID = new Guid("26F2F5B2-9C5A-4F99-A026-4946A068500F"); + + public static IOutputTextPane Instance { + get { + if (_instance is null) + throw new InvalidOperationException("Logger hasn't been initialized yet"); + return _instance; + } + set { + if (_instance is not null) + throw new InvalidOperationException("Can't initialize the logger twice"); + _instance = value ?? throw new ArgumentNullException(nameof(value)); + } + } + static IOutputTextPane? _instance; + + // This class initializes the Logger property. It gets auto loaded by dnSpy + [ExportAutoLoaded(Order = double.MinValue)] + sealed class InitializeLogger : IAutoLoaded { + [ImportingConstructor] + InitializeLogger(IOutputService outputService) { + Instance = outputService.Create(THE_GUID, "My Logger"); + Instance.WriteLine("Logger initialized!"); + } + } + } + + sealed class LogEditorCtxMenuContext { + public readonly IOutputTextPane TextPane; + + public LogEditorCtxMenuContext(IOutputTextPane pane) => TextPane = pane; + } + + abstract class LogEditorCtxMenuCommand : MenuItemBase { + protected sealed override object CachedContextKey => ContextKey; + static readonly object ContextKey = new object(); + + protected sealed override LogEditorCtxMenuContext? CreateContext(IMenuItemContext context) { + // Check if it's the Output window + if (context.CreatorObject.Guid != new Guid(MenuConstants.GUIDOBJ_LOG_TEXTEDITORCONTROL_GUID)) + return null; + + // Get the text pane if any + var textPane = context.Find(); + if (textPane is null) + return null; + + // Check if it's our logger text pane + if (textPane.Guid != MyLogger.THE_GUID) + return null; + + Debug.Assert(textPane == MyLogger.Instance); + + return new LogEditorCtxMenuContext(textPane); + } + } + + // GROUP_CTX_OUTPUT_USER_COMMANDS can be used for user commands, like our commands below: + [ExportMenuItem(Header = "Write Hello to the Log", Group = MenuConstants.GROUP_CTX_OUTPUT_USER_COMMANDS, Order = 0)] + sealed class WriteHelloCtxMenuCommand : LogEditorCtxMenuCommand { + public override void Execute(LogEditorCtxMenuContext context) { + context.TextPane.Write(TextColor.Blue, "H"); + context.TextPane.Write(TextColor.Red, "E"); + context.TextPane.Write(TextColor.Green, "L"); + context.TextPane.Write(TextColor.Yellow, "L"); + context.TextPane.Write(TextColor.Cyan, "O"); + context.TextPane.Write(TextColor.Gray, "!"); + context.TextPane.WriteLine(); + } + } + + [ExportMenuItem(Header = "Open the Pod Bay Doors", Group = MenuConstants.GROUP_CTX_OUTPUT_USER_COMMANDS, Order = 10)] + sealed class ShowExceptionMessagesCtxMenuCommand : LogEditorCtxMenuCommand { + public override void Execute(LogEditorCtxMenuContext context) => + context.TextPane.WriteLine(TextColor.Error, "I'm afraid I can't do that."); + } +} diff --git a/Extensions/Examples/Example2.Extension/README.md b/Extensions/Examples/Example2.Extension/README.md new file mode 100644 index 0000000..b7cca4f --- /dev/null +++ b/Extensions/Examples/Example2.Extension/README.md @@ -0,0 +1,12 @@ + +This extension shows how to do more advanced stuff. It: + +- Adds a tool window (ToolWindowContent.cs) +- Adds new tree nodes (TreeNodeDataProvider.cs) +- Adds custom tab content for the new AssemblyChildNode tree node (AssemblyChildNodeTabContent.cs). ModuleChildNode implements IDecompileSelf to decompile itself. +- Shows tooltips when hovering over custom references added to the text editor (DocumentViewerToolTipProvider.cs) +- Adds a new IDsDocument instance and DsDocumentNode node (NewDsDocument.cs). It opens .txt files and shows the output in the text editor. +- Colorizes text in text editors (Colorizer.cs) +- Colorizes text in hex editors (HexColorizer.cs) +- Colorizes Assembly Explorer treeview nodes (TreeViewNodeColorizer.cs) +- Adds a new Output window text pane (OutputTextPane.cs) diff --git a/Extensions/Examples/Example2.Extension/TheExtension.cs b/Extensions/Examples/Example2.Extension/TheExtension.cs new file mode 100644 index 0000000..89bebdc --- /dev/null +++ b/Extensions/Examples/Example2.Extension/TheExtension.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using dnSpy.Contracts.Extension; + +// Each extension should export one class implementing IExtension + +namespace Example2.Extension { + [ExportExtension] + sealed class TheExtension : IExtension { + public IEnumerable MergedResourceDictionaries { + get { + yield return "Themes/resourcedict.xaml"; + } + } + + public ExtensionInfo ExtensionInfo => new ExtensionInfo { + ShortDescription = "Example2 extension", + }; + + public void OnEvent(ExtensionEvent @event, object? obj) { + // We don't care about any events + } + } +} diff --git a/Extensions/Examples/Example2.Extension/Themes/resourcedict.xaml b/Extensions/Examples/Example2.Extension/Themes/resourcedict.xaml new file mode 100644 index 0000000..f25205a --- /dev/null +++ b/Extensions/Examples/Example2.Extension/Themes/resourcedict.xaml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + diff --git a/Extensions/Examples/Example2.Extension/ToolWindowContent.cs b/Extensions/Examples/Example2.Extension/ToolWindowContent.cs new file mode 100644 index 0000000..05c198f --- /dev/null +++ b/Extensions/Examples/Example2.Extension/ToolWindowContent.cs @@ -0,0 +1,170 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Windows; +using System.Windows.Input; +using dnSpy.Contracts.Controls; +using dnSpy.Contracts.Extension; +using dnSpy.Contracts.Menus; +using dnSpy.Contracts.MVVM; +using dnSpy.Contracts.ToolWindows; +using dnSpy.Contracts.ToolWindows.App; + +// Adds a tool window and a command that will show it. The command is added to the View menu and a +// keyboard shortcut is added to the main window. Keyboard shortcut Ctrl+Alt+Z shows the tool window. + +namespace Example2.Extension { + // Adds the 'OpenToolWindow' command to the main window and sets keyboard shortcut to Ctrl+Alt+Z + [ExportAutoLoaded] + sealed class ToolWindowLoader : IAutoLoaded { + public static readonly RoutedCommand OpenToolWindow = new RoutedCommand("OpenToolWindow", typeof(ToolWindowLoader)); + + [ImportingConstructor] + ToolWindowLoader(IWpfCommandService wpfCommandService, IDsToolWindowService toolWindowService) { + var cmds = wpfCommandService.GetCommands(ControlConstants.GUID_MAINWINDOW); + cmds.Add(OpenToolWindow, new RelayCommand(a => toolWindowService.Show(ToolWindowContentImpl.THE_GUID))); + cmds.Add(OpenToolWindow, ModifierKeys.Control | ModifierKeys.Alt, Key.Z); + } + } + + // Adds a menu item to the View menu to show the tool window + [ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_VIEW_GUID, Header = "Extension Tool Window", InputGestureText = "Ctrl+Alt+Z", Group = MenuConstants.GROUP_APP_MENU_VIEW_WINDOWS, Order = 2000)] + sealed class ViewCommand1 : MenuItemCommand { + ViewCommand1() + : base(ToolWindowLoader.OpenToolWindow) { + } + } + + // Dummy dependency "needed" by MainToolWindowContentProvider + [Export] + sealed class DeppDep { + public void Hello() { + } + } + + // Called by dnSpy to create the tool window + [Export(typeof(IToolWindowContentProvider))] + sealed class MainToolWindowContentProvider : IToolWindowContentProvider { + // Caches the created tool window + ToolWindowContentImpl ToolWindowContent => myToolWindowContent ??= new ToolWindowContentImpl(); + ToolWindowContentImpl? myToolWindowContent; + + // Add any deps to the constructor if needed, else remove the constructor + [ImportingConstructor] + MainToolWindowContentProvider(DeppDep deppDep) => deppDep.Hello(); + + // Lets dnSpy know which tool windows it can create and their default locations + public IEnumerable ContentInfos { + get { yield return new ToolWindowContentInfo(ToolWindowContentImpl.THE_GUID, ToolWindowContentImpl.DEFAULT_LOCATION, 0, false); } + } + + // Called by dnSpy. If it's your tool window guid, return the instance. Make sure it's + // cached since it can be called multiple times. + public ToolWindowContent? GetOrCreate(Guid guid) { + if (guid == ToolWindowContentImpl.THE_GUID) + return ToolWindowContent; + return null; + } + } + + sealed class ToolWindowContentImpl : ToolWindowContent { + //TODO: Use your own guid + public static readonly Guid THE_GUID = new Guid("18785447-21A8-41DB-B8AD-0F166AEC0D08"); + public const AppToolWindowLocation DEFAULT_LOCATION = AppToolWindowLocation.DefaultHorizontal; + + public override Guid Guid => THE_GUID; + public override string Title => "Extension Example"; + + // This is the object shown in the UI. Return a WPF object or a .NET object with a DataTemplate + public override object? UIObject => toolWindowControl; + + // The element inside UIObject that gets the focus when the tool window should be focused. + // If it's not as easy as calling FocusedElement.Focus() to focus it, you must implement + // dnSpy.Contracts.Controls.IFocusable. + public override IInputElement? FocusedElement => toolWindowControl.option1TextBox; + + // The element that gets scaled when the user zooms in or out. Return null if zooming isn't + // possible + public override FrameworkElement? ZoomElement => toolWindowControl; + + readonly ToolWindowControl toolWindowControl; + readonly ToolWindowVM toolWindowVM; + + public ToolWindowContentImpl() { + toolWindowControl = new ToolWindowControl(); + toolWindowVM = new ToolWindowVM(); + toolWindowControl.DataContext = toolWindowVM; + } + + // Gets notified when the content gets hidden, visible, etc. Can be used to tell the view + // model to stop doing stuff when it gets hidden in case it does a lot of work. + public override void OnVisibilityChanged(ToolWindowContentVisibilityEvent visEvent) { + switch (visEvent) { + case ToolWindowContentVisibilityEvent.Added: + toolWindowVM.IsEnabled = true; + break; + + case ToolWindowContentVisibilityEvent.Removed: + toolWindowVM.IsEnabled = false; + break; + + case ToolWindowContentVisibilityEvent.Visible: + toolWindowVM.IsVisible = true; + break; + + case ToolWindowContentVisibilityEvent.Hidden: + toolWindowVM.IsVisible = false; + break; + } + } + } + + sealed class ToolWindowVM : ViewModelBase { + public string StringOption1 { + get => stringOption1; + set { + if (stringOption1 != value) { + stringOption1 = value; + OnPropertyChanged(nameof(StringOption1)); + } + } + } + string stringOption1 = string.Empty; + + public string StringOption2 { + get => stringOption2; + set { + if (stringOption2 != value) { + stringOption2 = value; + OnPropertyChanged(nameof(StringOption2)); + } + } + } + string stringOption2 = string.Empty; + + public string StringOption3 { + get => stringOption3; + set { + if (stringOption3 != value) { + stringOption3 = value; + OnPropertyChanged(nameof(StringOption3)); + } + } + } + string stringOption3 = string.Empty; + + public string StringOption4 { + get => stringOption4; + set { + if (stringOption4 != value) { + stringOption4 = value; + OnPropertyChanged(nameof(StringOption4)); + } + } + } + string stringOption4 = string.Empty; + + public bool IsEnabled { get; set; } + public bool IsVisible { get; set; } + } +} diff --git a/Extensions/Examples/Example2.Extension/ToolWindowControl.xaml b/Extensions/Examples/Example2.Extension/ToolWindowControl.xaml new file mode 100644 index 0000000..557e3e2 --- /dev/null +++ b/Extensions/Examples/Example2.Extension/ToolWindowControl.xaml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/Extensions/Examples/Example2.Extension/ToolWindowControl.xaml.cs b/Extensions/Examples/Example2.Extension/ToolWindowControl.xaml.cs new file mode 100644 index 0000000..cb5a9a5 --- /dev/null +++ b/Extensions/Examples/Example2.Extension/ToolWindowControl.xaml.cs @@ -0,0 +1,7 @@ +using System.Windows.Controls; + +namespace Example2.Extension { + public partial class ToolWindowControl : UserControl { + public ToolWindowControl() => InitializeComponent(); + } +} diff --git a/Extensions/Examples/Example2.Extension/TreeNodeDataProvider.cs b/Extensions/Examples/Example2.Extension/TreeNodeDataProvider.cs new file mode 100644 index 0000000..a87941d --- /dev/null +++ b/Extensions/Examples/Example2.Extension/TreeNodeDataProvider.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Documents.Tabs.DocViewer; +using dnSpy.Contracts.Documents.TreeView; +using dnSpy.Contracts.Images; +using dnSpy.Contracts.Text; +using dnSpy.Contracts.TreeView; + +// This file contains classes that create new child nodes of IAssemblyFileNode and IModuleFileNode + +namespace Example2.Extension { + // This class adds a new child node to all assembly nodes + [ExportTreeNodeDataProvider(Guid = DocumentTreeViewConstants.ASSEMBLY_NODE_GUID)] + sealed class AssemblyTreeNodeDataProvider : ITreeNodeDataProvider { + public IEnumerable Create(TreeNodeDataProviderContext context) { + yield return new AssemblyChildNode(); + } + } + + // This class adds a new child node to all module nodes + [ExportTreeNodeDataProvider(Guid = DocumentTreeViewConstants.MODULE_NODE_GUID)] + sealed class ModuleTreeNodeDataProvider : ITreeNodeDataProvider { + public IEnumerable Create(TreeNodeDataProviderContext context) { + yield return new ModuleChildNode(); + } + } + + sealed class AssemblyChildNode : DocumentTreeNodeData { // All file tree nodes should implement DocumentTreeNodeData or derive from DocumentTreeNodeData + //TODO: Use your own guid + public static readonly Guid THE_GUID = new Guid("6CF91674-16CE-44EA-B9E8-80B68C327D30"); + + public override Guid Guid => THE_GUID; + public override NodePathName NodePathName => new NodePathName(Guid); + + // The image must be in an Images folder (in the resources) and have a .png extension + protected override ImageReference GetIcon(IDotNetImageService dnImgMgr) => DsImages.EntryPoint; + + protected override void WriteCore(ITextColorWriter output, IDecompiler decompiler, DocumentNodeWriteOptions options) => + output.Write(BoxedTextColor.Text, "Assembly Child"); + + // If you don't want the node to be appended to the children, override this + public override ITreeNodeGroup? TreeNodeGroup => TreeNodeGroupImpl.Instance; + + sealed class TreeNodeGroupImpl : ITreeNodeGroup { + public static readonly TreeNodeGroupImpl Instance = new TreeNodeGroupImpl(1); + + public TreeNodeGroupImpl(double order) => Order = order; + + public double Order { get; } + + public int Compare([AllowNull] TreeNodeData x, [AllowNull] TreeNodeData y) { + if (x == y) + return 0; + var a = x as AssemblyChildNode; + var b = y as AssemblyChildNode; + if (a is null) return -1; + if (b is null) return 1; + // More checks can be added here... + return 0; + } + } + } + + // This class can decompile its own output and implements IDecompileSelf + sealed class ModuleChildNode : DocumentTreeNodeData, IDecompileSelf { // All file tree nodes should implement DocumentTreeNodeData or derive from DocumentTreeNodeData + //TODO: Use your own guid + public static readonly Guid THE_GUID = new Guid("C8892F6C-6A49-4537-AAA0-D0DEF1E87277"); + + public override Guid Guid => THE_GUID; + public override NodePathName NodePathName => new NodePathName(Guid); + + // Initialize() is called after the constructor has been called, and after the TreeNode prop + // has been initialized + public override void Initialize() => + // Set LazyLoading if creating the children could take a while + TreeNode.LazyLoading = true; + + // If TreeNode.LazyLoading is false, CreateChildren() is called after Initialize(), else it's + // called when this node gets expanded. + public override IEnumerable CreateChildren() { + // Add some children in random order. They will be sorted because SomeMessageNode + // overrides the TreeNodeGroup prop. + yield return new SomeMessageNode("ZZZZZZZZZZZZZ"); + yield return new SomeMessageNode("AAAAaaaaaAAAAAAA"); + yield return new SomeMessageNode("SAY"); + } + + // The image must be in an Images folder (in the resources) and have a .png extension + protected override ImageReference GetIcon(IDotNetImageService dnImgMgr) => DsImages.String; + + protected override void WriteCore(ITextColorWriter output, IDecompiler decompiler, DocumentNodeWriteOptions options) => output.Write(BoxedTextColor.Text, "Module Child"); + + // Gets called by dnSpy if there's only one node to decompile. This method gets called in a + // worker thread. The output is also cached unless you call IDocumentViewerOutput.DisableCaching(). + public bool Decompile(IDecompileNodeContext context) { + // Pretend we actually do something... + Thread.Sleep(2000); + + // If you don't want the output to be cached, call DisableCaching() + bool cacheOutput = true; + if (!cacheOutput) + (context.Output as IDocumentViewerOutput)?.DisableCaching(); + + // Create the output and a few references that other code in this extension will use, eg. + // to show a tooltip when hovering over the reference. + context.ContentTypeString = ContentTypes.CSharp; + context.Output.WriteLine("// Initialize it to the secret key", BoxedTextColor.Comment); + context.Output.Write("int", new StringInfoReference("This is a reference added by the code"), DecompilerReferenceFlags.None, BoxedTextColor.Keyword); + context.Output.Write(" ", BoxedTextColor.Text); + context.Output.Write("secret", new StringInfoReference("The real secret is actually 42 not 1234"), DecompilerReferenceFlags.None, BoxedTextColor.Local); + context.Output.Write(" ", BoxedTextColor.Text); + context.Output.Write("=", BoxedTextColor.Operator); + context.Output.Write(" ", BoxedTextColor.Text); + context.Output.Write("1234", BoxedTextColor.Number); + context.Output.Write(";", BoxedTextColor.Punctuation); + context.Output.WriteLine(); + + // We decompiled ourselves so return true + return true; + } + + // If you don't want the node to be appended to the children, override this + public override ITreeNodeGroup? TreeNodeGroup => TreeNodeGroupImpl.Instance; + + sealed class TreeNodeGroupImpl : ITreeNodeGroup { + // Make sure the order is unique. 0 is already used by the PE node, so use 1 + public static readonly TreeNodeGroupImpl Instance = new TreeNodeGroupImpl(1); + + public TreeNodeGroupImpl(double order) => Order = order; + + public double Order { get; } + + public int Compare([AllowNull] TreeNodeData x, [AllowNull] TreeNodeData y) { + if (x == y) + return 0; + var a = x as ModuleChildNode; + var b = y as ModuleChildNode; + if (a is null) return -1; + if (b is null) return 1; + // More checks can be added here... + return 0; + } + } + } + + sealed class SomeMessageNode : DocumentTreeNodeData { + //TODO: Use your own guid + public static readonly Guid THE_GUID = new Guid("1751CD40-68CE-4F8A-84AF-99371B6FD843"); + + public string Message { get; } + + public SomeMessageNode(string msg) => Message = msg; + + public override Guid Guid => THE_GUID; + public override NodePathName NodePathName => new NodePathName(THE_GUID, Message); + protected override ImageReference GetIcon(IDotNetImageService dnImgMgr) => DsImages.String; + + protected override void WriteCore(ITextColorWriter output, IDecompiler decompiler, DocumentNodeWriteOptions options) => + output.Write(BoxedTextColor.Comment, Message); + + public override ITreeNodeGroup? TreeNodeGroup => TreeNodeGroupImpl.Instance; + + sealed class TreeNodeGroupImpl : ITreeNodeGroup { + public static readonly TreeNodeGroupImpl Instance = new TreeNodeGroupImpl(0); + + public TreeNodeGroupImpl(double order) => Order = order; + + public double Order { get; } + + public int Compare([AllowNull] TreeNodeData x, [AllowNull] TreeNodeData y) { + if (x == y) + return 0; + var a = x as SomeMessageNode; + var b = y as SomeMessageNode; + if (a is null) return -1; + if (b is null) return 1; + return StringComparer.OrdinalIgnoreCase.Compare(a.Message, b.Message); + } + } + } + + // SomeMessageNode doesn't implement IDecompileSelf, so we add a "decompiler" that can "decompile" + // those nodes. + [ExportDecompileNode] + sealed class SomeMessageNodeDecompiler : IDecompileNode { + public bool Decompile(IDecompileNodeContext context, DocumentTreeNodeData node) { + var msgNode = node as SomeMessageNode; + if (msgNode is null) + return false; + + context.Decompiler.WriteCommentLine(context.Output, "The secret message has been decrypted."); + context.Decompiler.WriteCommentLine(context.Output, $"The message is: {msgNode.Message}"); + context.ContentTypeString = ContentTypes.PlainText; + return true; + } + } +} diff --git a/Extensions/Examples/Example2.Extension/TreeViewNodeColorizer.cs b/Extensions/Examples/Example2.Extension/TreeViewNodeColorizer.cs new file mode 100644 index 0000000..2def6cb --- /dev/null +++ b/Extensions/Examples/Example2.Extension/TreeViewNodeColorizer.cs @@ -0,0 +1,91 @@ +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Windows.Media; +using dnSpy.Contracts.Documents.TreeView; +using dnSpy.Contracts.Text.Classification; +using dnSpy.Contracts.TreeView.Text; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Utilities; + +// Adds an underline to Assembly Explorer nodes: Assembly / Method +// Adds light green background in the middle of all text + +namespace Example2.Extension { + static class TreeViewNodeColorizerClassifications { + public const string UnderlineClassificationType = "Example2.Extension.UnderlineClassificationType"; + public const string LightgreenBackgroundClassificationType = "Example2.Extension.LightgreenBackgroundClassificationType"; + + // Disable compiler warnings. The fields aren't referenced, just exported so + // the metadata can be added to some table. The fields will always be null. +#pragma warning disable CS0169 + // Export the classes that define the name, and base types + [Export(typeof(ClassificationTypeDefinition))] + [Name(UnderlineClassificationType)] + [BaseDefinition(PredefinedClassificationTypeNames.FormalLanguage)] + static ClassificationTypeDefinition? UnderlineClassificationTypeDefinition; + + [Export(typeof(ClassificationTypeDefinition))] + [Name(LightgreenBackgroundClassificationType)] + [BaseDefinition(PredefinedClassificationTypeNames.FormalLanguage)] + static ClassificationTypeDefinition? LightgreenBackgroundClassificationTypeDefinition; +#pragma warning restore CS0169 + + // Export the classes that define the colors and order + [Export(typeof(EditorFormatDefinition))] + [ClassificationType(ClassificationTypeNames = UnderlineClassificationType)] + [Name("Underline")] + [UserVisible(true)] + [Order(After = Priority.Default)] + sealed class UnderlineClassificationFormatDefinition : ClassificationFormatDefinition { + UnderlineClassificationFormatDefinition() => TextDecorations = System.Windows.TextDecorations.Underline; + } + + [Export(typeof(EditorFormatDefinition))] + [ClassificationType(ClassificationTypeNames = LightgreenBackgroundClassificationType)] + [Name("Lightgreen Background")] + [UserVisible(true)] + [Order(After = Priority.Default)] + sealed class LightGreenBackgroundClassificationFormatDefinition : ClassificationFormatDefinition { + LightGreenBackgroundClassificationFormatDefinition() => BackgroundBrush = Brushes.LightGreen; + } + } + + [Export(typeof(ITextClassifierProvider))] + // You can also add more content types or use the base content type TreeViewContentTypes.TreeViewNode + [ContentType(TreeViewContentTypes.TreeViewNodeAssemblyExplorer)] + sealed class TreeViewNodeColorizerProvider : ITextClassifierProvider { + readonly IClassificationTypeRegistryService classificationTypeRegistryService; + + [ImportingConstructor] + TreeViewNodeColorizerProvider(IClassificationTypeRegistryService classificationTypeRegistryService) => this.classificationTypeRegistryService = classificationTypeRegistryService; + + public ITextClassifier? Create(IContentType contentType) => new TreeViewNodeColorizer(classificationTypeRegistryService); + } + + sealed class TreeViewNodeColorizer : ITextClassifier { + readonly IClassificationTypeRegistryService classificationTypeRegistryService; + + public TreeViewNodeColorizer(IClassificationTypeRegistryService classificationTypeRegistryService) => this.classificationTypeRegistryService = classificationTypeRegistryService; + + public IEnumerable GetTags(TextClassifierContext context) { + var tvContext = context as TreeViewNodeClassifierContext; + if (tvContext is null) + yield break; + + // Don't do a thing if it's a tooltip + if (tvContext.IsToolTip) + yield break; + + // Add the underline + if (tvContext.Node is AssemblyDocumentNode || tvContext.Node is MethodNode) { + yield return new TextClassificationTag(new Span(0, context.Text.Length), + classificationTypeRegistryService.GetClassificationType(TreeViewNodeColorizerClassifications.UnderlineClassificationType)); + } + + // Add light green background in the middle of the text + yield return new TextClassificationTag(new Span(context.Text.Length / 4, context.Text.Length / 2), + classificationTypeRegistryService.GetClassificationType(TreeViewNodeColorizerClassifications.LightgreenBackgroundClassificationType)); + } + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/CSharp/AssemblyInfoTransform.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/CSharp/AssemblyInfoTransform.cs new file mode 100644 index 0000000..3be97f1 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/CSharp/AssemblyInfoTransform.cs @@ -0,0 +1,71 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System.Diagnostics; +using System.Linq; +using dnlib.DotNet; +using ICSharpCode.Decompiler.Ast.Transforms; +using ICSharpCode.NRefactory.CSharp; + +namespace dnSpy.Decompiler.ILSpy.Core.CSharp { + sealed class AssemblyInfoTransform : IAstTransform { + public void Run(AstNode compilationUnit) { + foreach (var attrSect in compilationUnit.Descendants.OfType()) { + var attr = attrSect.Descendants.OfType().FirstOrDefault(); + Debug2.Assert(attr is not null); + if (attr is null) + continue; + bool remove = false; + if (!remove && attr.Annotation() is CustomAttribute ca) { + remove = + Compare(ca.AttributeType, systemRuntimeVersioningString, targetFrameworkAttributeString) || + Compare(ca.AttributeType, systemSecurityString, unverifiableCodeAttributeString) || + Compare(ca.AttributeType, systemRuntimeCompilerServicesyString, compilationRelaxationsAttributeString) || + Compare(ca.AttributeType, systemRuntimeCompilerServicesyString, runtimeCompatibilityAttributeString) || + Compare(ca.AttributeType, systemDiagnosticsString, debuggableAttributeString); + } + if (!remove && attr.Annotation() is SecurityAttribute) + remove = true; + if (remove) + attrSect.Remove(); + } + } + static readonly UTF8String systemRuntimeVersioningString = new UTF8String("System.Runtime.Versioning"); + static readonly UTF8String targetFrameworkAttributeString = new UTF8String("TargetFrameworkAttribute"); + static readonly UTF8String systemSecurityString = new UTF8String("System.Security"); + static readonly UTF8String unverifiableCodeAttributeString = new UTF8String("UnverifiableCodeAttribute"); + static readonly UTF8String systemRuntimeCompilerServicesyString = new UTF8String("System.Runtime.CompilerServices"); + static readonly UTF8String compilationRelaxationsAttributeString = new UTF8String("CompilationRelaxationsAttribute"); + static readonly UTF8String runtimeCompatibilityAttributeString = new UTF8String("RuntimeCompatibilityAttribute"); + static readonly UTF8String systemDiagnosticsString = new UTF8String("System.Diagnostics"); + static readonly UTF8String debuggableAttributeString = new UTF8String("DebuggableAttribute"); + + static bool Compare(ITypeDefOrRef type, UTF8String expNs, UTF8String expName) { + if (type is null) + return false; + + if (type is TypeRef tr) + return tr.Namespace == expNs && tr.Name == expName; + if (type is TypeDef td) + return td.Namespace == expNs && td.Name == expName; + + return false; + } + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/CSharp/BuilderCache.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/CSharp/BuilderCache.cs new file mode 100644 index 0000000..5f42b6e --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/CSharp/BuilderCache.cs @@ -0,0 +1,90 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using dnlib.DotNet; +using dnSpy.Contracts.Decompiler; +using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.Ast; + +namespace dnSpy.Decompiler.ILSpy.Core.CSharp { + /// + /// State for one decompiler thread. There should be at most one of these per CPU. This class + /// is not thread safe and must only be accessed by the owner thread. + /// + sealed class AstBuilderState { + public readonly AstBuilder AstBuilder; + + /// + /// instance used by XML doc code. This is always in a random + /// state (random text) and caller must Clear() it before use. + /// + public readonly StringBuilder XmlDoc_StringBuilder; + + readonly Dictionary hasXmlDocFile; + ModuleDef? lastModule; + bool lastModuleResult; + + public AstBuilderState(int settingsVersion) { + AstBuilder = new AstBuilder(new DecompilerContext(settingsVersion, null, null, true)); + XmlDoc_StringBuilder = new StringBuilder(); + hasXmlDocFile = new Dictionary(); + } + + public bool? HasXmlDocFile(ModuleDef module) { + if (lastModule == module) + return lastModuleResult; + if (hasXmlDocFile.TryGetValue(module, out var res)) { + lastModule = module; + lastModuleResult = res; + return res; + } + return null; + } + + public void SetHasXmlDocFile(ModuleDef module, bool value) { + lastModule = module; + lastModuleResult = value; + hasXmlDocFile.Add(module, value); + } + + /// + /// Called to re-use this instance for another decompilation. Only the fields that need + /// resetting will be reset. + /// + public void Reset() => AstBuilder.Reset(); + } + + /// + /// One instance is created and stored in . It's used by the + /// decompiler threads to get an instance. + /// + sealed class BuilderCache { + readonly ThreadSafeObjectPool astBuilderStatePool; + + public BuilderCache(int settingsVersion) => astBuilderStatePool = new ThreadSafeObjectPool(Environment.ProcessorCount, () => new AstBuilderState(settingsVersion), resetAstBuilderState); + + static readonly Action resetAstBuilderState = abs => abs.Reset(); + + public AstBuilderState AllocateAstBuilderState() => astBuilderStatePool.Allocate(); + public void Free(AstBuilderState state) => astBuilderStatePool.Free(state); + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/CSharp/BuilderState.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/CSharp/BuilderState.cs new file mode 100644 index 0000000..30a3065 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/CSharp/BuilderState.cs @@ -0,0 +1,45 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; +using dnSpy.Contracts.Decompiler; +using ICSharpCode.Decompiler.Ast; + +namespace dnSpy.Decompiler.ILSpy.Core.CSharp { + /// + /// Gets the from the pool and returns it when + /// gets called. + /// + struct BuilderState : IDisposable { + public AstBuilder AstBuilder => State.AstBuilder; + + public readonly AstBuilderState State; + readonly BuilderCache cache; + + public BuilderState(DecompilationContext ctx, BuilderCache cache, MetadataTextColorProvider metadataTextColorProvider) { + this.cache = cache; + State = cache.AllocateAstBuilderState(); + State.AstBuilder.Context.CalculateILSpans = ctx.CalculateILSpans; + State.AstBuilder.Context.MetadataTextColorProvider = metadataTextColorProvider; + State.AstBuilder.Context.AsyncMethodBodyDecompilation = ctx.AsyncMethodBodyDecompilation; + } + + public void Dispose() => cache.Free(State); + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/CSharp/CSharpDecompiler.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/CSharp/CSharpDecompiler.cs new file mode 100644 index 0000000..55b949f --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/CSharp/CSharpDecompiler.cs @@ -0,0 +1,525 @@ +// 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 System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Xml; +using dnlib.DotNet; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Decompiler.XmlDoc; +using dnSpy.Contracts.Text; +using dnSpy.Decompiler.ILSpy.Core.Settings; +using dnSpy.Decompiler.ILSpy.Core.Text; +using dnSpy.Decompiler.ILSpy.Core.XmlDoc; +using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.Ast; +using ICSharpCode.Decompiler.Ast.Transforms; +using ICSharpCode.NRefactory.CSharp; + +namespace dnSpy.Decompiler.ILSpy.Core.CSharp { + sealed class DecompilerProvider : IDecompilerProvider { + readonly DecompilerSettingsService decompilerSettingsService; + + // Keep the default ctor. It's used by dnSpy.Console.exe + public DecompilerProvider() + : this(DecompilerSettingsService.__Instance_DONT_USE) { + } + + public DecompilerProvider(DecompilerSettingsService decompilerSettingsService) { + Debug2.Assert(decompilerSettingsService is not null); + this.decompilerSettingsService = decompilerSettingsService ?? throw new ArgumentNullException(nameof(decompilerSettingsService)); + } + + public IEnumerable Create() { + yield return new CSharpDecompiler(decompilerSettingsService.CSharpVBDecompilerSettings, DecompilerConstants.CSHARP_ILSPY_ORDERUI); +#if DEBUG + foreach (var l in CSharpDecompiler.GetDebugDecompilers(decompilerSettingsService.CSharpVBDecompilerSettings)) + yield return l; +#endif + } + } + + /// + /// Decompiler logic for C#. + /// + sealed class CSharpDecompiler : DecompilerBase { + string uniqueNameUI = "C#"; + Guid uniqueGuid = DecompilerConstants.LANGUAGE_CSHARP_ILSPY; + bool showAllMembers = false; + readonly Func createBuilderCache; + Predicate? transformAbortCondition = null; + + public override DecompilerSettingsBase Settings => langSettings; + readonly CSharpVBDecompilerSettings langSettings; + + public CSharpDecompiler(CSharpVBDecompilerSettings langSettings, double orderUI) { + this.langSettings = langSettings; + createBuilderCache = () => new BuilderCache(this.langSettings.Settings.SettingsVersion); + OrderUI = orderUI; + } + +#if DEBUG + internal static IEnumerable GetDebugDecompilers(CSharpVBDecompilerSettings langSettings) { + DecompilerContext context = new DecompilerContext(0, new ModuleDefUser("dummy"), CSharpMetadataTextColorProvider.Instance); + string lastTransformName = "no transforms"; + double orderUI = DecompilerConstants.CSHARP_ILSPY_DEBUG_ORDERUI; + uint id = 0xBF67AF3F; + foreach (Type _transformType in TransformationPipeline.CreatePipeline(context).Select(v => v.GetType()).Distinct()) { + Type transformType = _transformType; // copy for lambda + yield return new CSharpDecompiler(langSettings, orderUI++) { + transformAbortCondition = v => transformType.IsInstanceOfType(v), + uniqueNameUI = "C# - " + lastTransformName, + uniqueGuid = new Guid($"203F702E-7E87-4F01-84CD-B0E8{id++:X8}"), + showAllMembers = true + }; + lastTransformName = "after " + transformType.Name; + } + yield return new CSharpDecompiler(langSettings, orderUI++) { + uniqueNameUI = "C# - " + lastTransformName, + uniqueGuid = new Guid($"203F702E-7E87-4F01-84CD-B0E8{id++:X8}"), + showAllMembers = true + }; + } +#endif + + public override string ContentTypeString => ContentTypesInternal.CSharpILSpy; + public override string GenericNameUI => DecompilerConstants.GENERIC_NAMEUI_CSHARP; + public override string UniqueNameUI => uniqueNameUI; + public override double OrderUI { get; } + public override Guid GenericGuid => DecompilerConstants.LANGUAGE_CSHARP; + public override Guid UniqueGuid => uniqueGuid; + public override string FileExtension => ".cs"; + public override string? ProjectFileExtension => ".csproj"; + + public override void Decompile(MethodDef method, IDecompilerOutput output, DecompilationContext ctx) { + WriteCommentLineDeclaringType(output, method); + var state = CreateAstBuilder(ctx, langSettings.Settings, currentType: method.DeclaringType, isSingleMember: true); + try { + if (method.IsConstructor && !method.IsStatic && !method.DeclaringType.IsValueType) { + // also fields and other ctors so that the field initializers can be shown as such + AddFieldsAndCtors(state.AstBuilder, method.DeclaringType, method.IsStatic); + RunTransformsAndGenerateCode(ref state, output, ctx, new SelectCtorTransform(method)); + } + else { + state.AstBuilder.AddMethod(method); + RunTransformsAndGenerateCode(ref state, output, ctx); + } + } + finally { + state.Dispose(); + } + } + + class SelectCtorTransform : IAstTransform { + readonly MethodDef ctorDef; + + public SelectCtorTransform(MethodDef ctorDef) => this.ctorDef = ctorDef; + + public void Run(AstNode compilationUnit) { + ConstructorDeclaration? ctorDecl = null; + foreach (var node in compilationUnit.Children) { + if (node is ConstructorDeclaration ctor) { + if (ctor.Annotation() == ctorDef) { + ctorDecl = ctor; + } + else { + // remove other ctors + ctor.Remove(); + } + } + // Remove any fields without initializers + if (node is FieldDeclaration fd && fd.Variables.All(v => v.Initializer.IsNull)) + fd.Remove(); + } + if (ctorDecl?.Initializer.ConstructorInitializerType == ConstructorInitializerType.This) { + // remove all fields + foreach (var node in compilationUnit.Children) + if (node is FieldDeclaration) + node.Remove(); + } + } + } + + public override void Decompile(PropertyDef property, IDecompilerOutput output, DecompilationContext ctx) { + WriteCommentLineDeclaringType(output, property); + var state = CreateAstBuilder(ctx, langSettings.Settings, currentType: property.DeclaringType, isSingleMember: true); + try { + state.AstBuilder.AddProperty(property); + RunTransformsAndGenerateCode(ref state, output, ctx); + } + finally { + state.Dispose(); + } + } + + public override void Decompile(FieldDef field, IDecompilerOutput output, DecompilationContext ctx) { + WriteCommentLineDeclaringType(output, field); + var state = CreateAstBuilder(ctx, langSettings.Settings, currentType: field.DeclaringType, isSingleMember: true); + try { + if (field.IsLiteral) { + state.AstBuilder.AddField(field); + } + else { + // also decompile ctors so that the field initializer can be shown + AddFieldsAndCtors(state.AstBuilder, field.DeclaringType, field.IsStatic); + } + RunTransformsAndGenerateCode(ref state, output, ctx, new SelectFieldTransform(field)); + } + finally { + state.Dispose(); + } + } + + /// + /// Removes all top-level members except for the specified fields. + /// + sealed class SelectFieldTransform : IAstTransform { + readonly FieldDef field; + + public SelectFieldTransform(FieldDef field) => this.field = field; + + public void Run(AstNode compilationUnit) { + foreach (var child in compilationUnit.Children) { + if (child is EntityDeclaration) { + if (child.Annotation() != field) + child.Remove(); + } + } + } + } + + void AddFieldsAndCtors(AstBuilder codeDomBuilder, TypeDef declaringType, bool isStatic) { + foreach (var field in declaringType.Fields) { + if (field.IsStatic == isStatic) + codeDomBuilder.AddField(field); + } + foreach (var ctor in declaringType.Methods) { + if (ctor.IsConstructor && ctor.IsStatic == isStatic) + codeDomBuilder.AddMethod(ctor); + } + } + + public override void Decompile(EventDef ev, IDecompilerOutput output, DecompilationContext ctx) { + WriteCommentLineDeclaringType(output, ev); + var state = CreateAstBuilder(ctx, langSettings.Settings, currentType: ev.DeclaringType, isSingleMember: true); + try { + state.AstBuilder.AddEvent(ev); + RunTransformsAndGenerateCode(ref state, output, ctx); + } + finally { + state.Dispose(); + } + } + + public override void Decompile(TypeDef type, IDecompilerOutput output, DecompilationContext ctx) { + var state = CreateAstBuilder(ctx, langSettings.Settings, currentType: type); + try { + state.AstBuilder.AddType(type); + RunTransformsAndGenerateCode(ref state, output, ctx); + } + finally { + state.Dispose(); + } + } + + void RunTransformsAndGenerateCode(ref BuilderState state, IDecompilerOutput output, DecompilationContext ctx, IAstTransform? additionalTransform = null) { + var astBuilder = state.AstBuilder; + astBuilder.RunTransformations(transformAbortCondition); + if (additionalTransform is not null) { + additionalTransform.Run(astBuilder.SyntaxTree); + } + AddXmlDocumentation(ref state, langSettings.Settings, astBuilder); + astBuilder.GenerateCode(output); + } + + internal static void AddXmlDocumentation(ref BuilderState state, DecompilerSettings settings, AstBuilder astBuilder) { + if (settings.ShowXmlDocumentation) { + var module = state.AstBuilder.Context.CurrentModule; + var hasXmlDocFileTmp = state.State.HasXmlDocFile(module); + bool hasXmlDocFile; + if (hasXmlDocFileTmp is null) { + hasXmlDocFile = XmlDocLoader.LoadDocumentation(module) is not null; + state.State.SetHasXmlDocFile(module, hasXmlDocFile); + } + else + hasXmlDocFile = hasXmlDocFileTmp.Value; + if (!hasXmlDocFile) + return; + + try { + new AddXmlDocTransform(state.State.XmlDoc_StringBuilder).Run(astBuilder.SyntaxTree); + } + catch (XmlException ex) { + string[] msg = (" Exception while reading XmlDoc: " + ex.ToString()).Split(newLineChars, StringSplitOptions.RemoveEmptyEntries); + var insertionPoint = astBuilder.SyntaxTree.FirstChild; + for (int i = 0; i < msg.Length; i++) + astBuilder.SyntaxTree.InsertChildBefore(insertionPoint, new Comment(msg[i], CommentType.Documentation), Roles.Comment); + } + } + } + static readonly char[] newLineChars = new char[] { '\r', '\n', '\u0085', '\u2028', '\u2029' }; + + public override void Decompile(AssemblyDef asm, IDecompilerOutput output, DecompilationContext ctx) { + WriteAssembly(asm, output, ctx); + + using (ctx.DisableAssemblyLoad()) { + var state = CreateAstBuilder(ctx, langSettings.Settings, currentModule: asm.ManifestModule); + try { + state.AstBuilder.AddAssembly(asm.ManifestModule, true, true, false); + RunTransformsAndGenerateCode(ref state, output, ctx); + } + finally { + state.Dispose(); + } + } + } + + public override void Decompile(ModuleDef mod, IDecompilerOutput output, DecompilationContext ctx) { + WriteModule(mod, output, ctx); + + using (ctx.DisableAssemblyLoad()) { + var state = CreateAstBuilder(ctx, langSettings.Settings, currentModule: mod); + try { + state.AstBuilder.AddAssembly(mod, true, false, true); + RunTransformsAndGenerateCode(ref state, output, ctx); + } + finally { + state.Dispose(); + } + } + } + + BuilderState CreateAstBuilder(DecompilationContext ctx, DecompilerSettings settings, ModuleDef? currentModule = null, TypeDef? currentType = null, bool isSingleMember = false) { + if (currentModule is null) + currentModule = currentType?.Module; + if (isSingleMember) { + settings = settings.Clone(); + settings.UsingDeclarations = false; + } + var cache = ctx.GetOrCreate(createBuilderCache); + var state = new BuilderState(ctx, cache, MetadataTextColorProvider); + state.AstBuilder.Context.CurrentModule = currentModule; + state.AstBuilder.Context.CancellationToken = ctx.CancellationToken; + state.AstBuilder.Context.CurrentType = currentType; + state.AstBuilder.Context.Settings = settings; + return state; + } + + protected override void TypeToString(IDecompilerOutput output, ITypeDefOrRef? type, bool includeNamespace, IHasCustomAttribute? typeAttributes = null) { + ConvertTypeOptions options = ConvertTypeOptions.IncludeTypeParameterDefinitions; + if (includeNamespace) + options |= ConvertTypeOptions.IncludeNamespace; + + TypeToString(output, options, type, typeAttributes); + } + + static readonly UTF8String systemRuntimeCompilerServicesString = new UTF8String("System.Runtime.CompilerServices"); + static readonly UTF8String isReadOnlyAttributeString = new UTF8String("IsReadOnlyAttribute"); + bool WriteRefIfByRef(IDecompilerOutput output, TypeSig typeSig, ParamDef? pd) { + if (typeSig.RemovePinnedAndModifiers() is ByRefSig) { + if (pd is not null && (!pd.IsIn && pd.IsOut)) { + output.Write("out", BoxedTextColor.Keyword); + output.Write(" ", BoxedTextColor.Text); + } + else if (pd is not null && pd.IsDefined(systemRuntimeCompilerServicesString, isReadOnlyAttributeString)) { + output.Write("in", BoxedTextColor.Keyword); + output.Write(" ", BoxedTextColor.Text); + } + else { + output.Write("ref", BoxedTextColor.Keyword); + output.Write(" ", BoxedTextColor.Text); + } + return true; + } + return false; + } + + void TypeToString(IDecompilerOutput output, ConvertTypeOptions options, ITypeDefOrRef? type, IHasCustomAttribute? typeAttributes = null) { + if (type is null) + return; + AstType astType = AstBuilder.ConvertType(type, new StringBuilder(), typeAttributes, options); + + if (WriteRefIfByRef(output, type.TryGetByRefSig(), typeAttributes as ParamDef)) { + if (astType is ComposedType && ((ComposedType)astType).PointerRank > 0) + ((ComposedType)astType).PointerRank--; + } + + var ctx = new DecompilerContext(langSettings.Settings.SettingsVersion, type.Module, MetadataTextColorProvider); + astType.AcceptVisitor(new CSharpOutputVisitor(new TextTokenWriter(output, ctx), FormattingOptionsFactory.CreateAllman())); + } + + protected override void FormatPropertyName(IDecompilerOutput output, PropertyDef property, bool? isIndexer) { + if (property is null) + throw new ArgumentNullException(nameof(property)); + + if (!isIndexer.HasValue) { + isIndexer = property.IsIndexer(); + } + if (isIndexer.Value) { + var accessor = property.GetMethod ?? property.SetMethod; + if (accessor is not null && accessor.HasOverrides) { + var methDecl = accessor.Overrides.First().MethodDeclaration; + var declaringType = methDecl is null ? null : methDecl.DeclaringType; + TypeToString(output, declaringType, includeNamespace: true); + output.Write(".", BoxedTextColor.Operator); + } + output.Write("this", BoxedTextColor.Keyword); + output.Write("[", BoxedTextColor.Punctuation); + bool addSeparator = false; + foreach (var p in property.PropertySig.GetParams()) { + if (addSeparator) { + output.Write(",", BoxedTextColor.Punctuation); + output.Write(" ", BoxedTextColor.Text); + } + else + addSeparator = true; + TypeToString(output, p.ToTypeDefOrRef(), includeNamespace: true); + } + output.Write("]", BoxedTextColor.Punctuation); + } + else + WriteIdentifier(output, property.Name, MetadataTextColorProvider.GetColor(property)); + } + + static readonly HashSet isKeyword = new HashSet(StringComparer.Ordinal) { + "abstract", "as", "base", "bool", "break", "byte", "case", "catch", + "char", "checked", "class", "const", "continue", "decimal", "default", "delegate", + "do", "double", "else", "enum", "event", "explicit", "extern", "false", + "finally", "fixed", "float", "for", "foreach", "goto", "if", "implicit", + "in", "int", "interface", "internal", "is", "lock", "long", "namespace", + "new", "null", "object", "operator", "out", "override", "params", "private", + "protected", "public", "readonly", "ref", "return", "sbyte", "sealed", "short", + "sizeof", "stackalloc", "static", "string", "struct", "switch", "this", "throw", + "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", + "using", "virtual", "void", "volatile", "while", + }; + + static void WriteIdentifier(IDecompilerOutput output, string id, object tokenKind) { + if (isKeyword.Contains(id)) + output.Write("@", tokenKind); + output.Write(IdentifierEscaper.Escape(id), tokenKind); + } + + protected override void FormatTypeName(IDecompilerOutput output, TypeDef type) { + if (type is null) + throw new ArgumentNullException(nameof(type)); + + TypeToString(output, ConvertTypeOptions.DoNotUsePrimitiveTypeNames | ConvertTypeOptions.IncludeTypeParameterDefinitions | ConvertTypeOptions.DoNotIncludeEnclosingType, type); + } + + internal static bool ShowMember(IMemberRef member, bool showAllMembers, DecompilerSettings settings) { + if (showAllMembers) + return true; + if (member is MethodDef md && (md.IsGetter || md.IsSetter || md.IsAddOn || md.IsRemoveOn)) + return true; + return !AstBuilder.MemberIsHidden(member, settings); + } + + public override bool ShowMember(IMemberRef member) => ShowMember(member, showAllMembers, langSettings.Settings); + + public override bool CanDecompile(DecompilationType decompilationType) { + switch (decompilationType) { + case DecompilationType.PartialType: + case DecompilationType.AssemblyInfo: + case DecompilationType.TypeMethods: + return true; + } + return base.CanDecompile(decompilationType); + } + + public override void Decompile(DecompilationType decompilationType, object data) { + switch (decompilationType) { + case DecompilationType.PartialType: + DecompilePartial((DecompilePartialType)data); + return; + case DecompilationType.AssemblyInfo: + DecompileAssemblyInfo((DecompileAssemblyInfo)data); + return; + case DecompilationType.TypeMethods: + DecompileTypeMethods((DecompileTypeMethods)data); + return; + } + base.Decompile(decompilationType, data); + } + + void DecompilePartial(DecompilePartialType info) { + var state = CreateAstBuilder(info.Context, CreateDecompilerSettings(langSettings.Settings, info.UseUsingDeclarations), currentType: info.Type); + try { + state.AstBuilder.AddType(info.Type); + RunTransformsAndGenerateCode(ref state, info.Output, info.Context, new DecompilePartialTransform(info.Type, info.Definitions, info.ShowDefinitions, info.AddPartialKeyword, info.InterfacesToRemove)); + } + finally { + state.Dispose(); + } + } + + void DecompileAssemblyInfo(DecompileAssemblyInfo info) { + var state = CreateAstBuilder(info.Context, langSettings.Settings, currentModule: info.Module); + try { + state.AstBuilder.AddAssembly(info.Module, true, info.Module.IsManifestModule, true); + RunTransformsAndGenerateCode(ref state, info.Output, info.Context, info.KeepAllAttributes ? null : new AssemblyInfoTransform()); + } + finally { + state.Dispose(); + } + } + + void DecompileTypeMethods(DecompileTypeMethods info) { + var state = CreateAstBuilder(info.Context, CreateDecompilerSettings_DecompileTypeMethods(langSettings.Settings, !info.DecompileHidden, info.ShowAll), currentType: info.Type); + try { + state.AstBuilder.GetDecompiledBodyKind = (builder, method) => GetDecompiledBodyKind(info, builder, method); + state.AstBuilder.AddType(info.Type); + RunTransformsAndGenerateCode(ref state, info.Output, info.Context, new DecompileTypeMethodsTransform(info.Types, info.Methods, !info.DecompileHidden, info.ShowAll)); + } + finally { + state.Dispose(); + } + } + + internal static DecompilerSettings CreateDecompilerSettings_DecompileTypeMethods(DecompilerSettings settings, bool useUsingDeclarations, bool showAll) { + var s = CreateDecompilerSettings(settings, useUsingDeclarations); + // Make sure the ctor is shown if the user tries to edit an empty ctor/cctor + s.RemoveEmptyDefaultConstructors = false; + if (!showAll) { + // Inline all field initialization code + s.AllowFieldInitializers = false; + } + return s; + } + + internal static DecompilerSettings CreateDecompilerSettings(DecompilerSettings settings, bool useUsingDeclarations) { + var newOne = settings.Clone(); + newOne.UsingDeclarations = useUsingDeclarations; + newOne.FullyQualifyAllTypes = !useUsingDeclarations; + newOne.RemoveNewDelegateClass = useUsingDeclarations; + newOne.ForceShowAllMembers = false; + return newOne; + } + + internal static DecompiledBodyKind GetDecompiledBodyKind(DecompileTypeMethods info, AstBuilder builder, MethodDef method) { + if (info.DecompileHidden) + return DecompiledBodyKind.Empty; + if (info.ShowAll || info.Methods.Contains(method)) + return DecompiledBodyKind.Full; + return DecompiledBodyKind.Empty; + } + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/CSharp/DecompilePartialTransform.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/CSharp/DecompilePartialTransform.cs new file mode 100644 index 0000000..ca9e550 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/CSharp/DecompilePartialTransform.cs @@ -0,0 +1,84 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using dnlib.DotNet; +using ICSharpCode.Decompiler.Ast.Transforms; +using ICSharpCode.NRefactory.CSharp; + +namespace dnSpy.Decompiler.ILSpy.Core.CSharp { + sealed class DecompilePartialTransform : IAstTransform { + readonly TypeDef type; + readonly HashSet definitions; + readonly bool showDefinitions; + readonly bool addPartialKeyword; + readonly HashSet ifacesToRemove; + + public DecompilePartialTransform(TypeDef type, HashSet definitions, bool showDefinitions, bool addPartialKeyword, IEnumerable ifacesToRemove) { + this.type = type; + this.definitions = definitions; + this.showDefinitions = showDefinitions; + this.addPartialKeyword = addPartialKeyword; + this.ifacesToRemove = new HashSet(ifacesToRemove, TypeEqualityComparer.Instance); + } + + public void Run(AstNode compilationUnit) { + foreach (var en in compilationUnit.Descendants.OfType()) { + var def = en.Annotation(); + Debug2.Assert(def is not null); + if (def is null) + continue; + if (def == type) { + var tdecl = en as TypeDeclaration; + Debug2.Assert(tdecl is not null); + if (tdecl is not null) { + if (addPartialKeyword) { + if (tdecl.ClassType != ClassType.Enum) + tdecl.Modifiers |= Modifiers.Partial; + + // Make sure the comments are still shown before the method and its modifiers + var comments = en.GetChildrenByRole(Roles.Comment).Reverse().ToArray(); + foreach (var c in comments) { + c.Remove(); + en.InsertChildAfter(null, c, Roles.Comment); + } + } + foreach (var iface in tdecl.BaseTypes) { + var tdr = iface.Annotation(); + if (tdr is not null && ifacesToRemove.Contains(tdr)) + iface.Remove(); + } + } + } + else { + if (showDefinitions) { + if (!definitions.Contains(def)) + en.Remove(); + } + else { + if (definitions.Contains(def)) + en.Remove(); + } + } + } + } + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/CSharp/DecompileTypeMethodsTransform.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/CSharp/DecompileTypeMethodsTransform.cs new file mode 100644 index 0000000..d7d7a71 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/CSharp/DecompileTypeMethodsTransform.cs @@ -0,0 +1,137 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using dnlib.DotNet; +using ICSharpCode.Decompiler.Ast.Transforms; +using ICSharpCode.NRefactory.CSharp; + +namespace dnSpy.Decompiler.ILSpy.Core.CSharp { + sealed class DecompileTypeMethodsTransform : IAstTransform { + readonly HashSet defsToShow; + readonly HashSet partialTypes; + readonly bool showDefinitions; + readonly bool showAll; + + public DecompileTypeMethodsTransform(HashSet types, HashSet methods, bool showDefinitions, bool showAll) { + defsToShow = new HashSet(); + partialTypes = new HashSet(); + this.showDefinitions = showDefinitions; + this.showAll = showAll; + + foreach (var method in methods) { + // If it's part of a property or event, include the property or event since there are no partial props/events + var prop = method.DeclaringType.Properties.FirstOrDefault(a => a.GetMethods.Contains(method) || a.SetMethods.Contains(method)); + if (prop is not null) { + defsToShow.Add(prop); + foreach (var m in prop.GetMethods) + defsToShow.Add(m); + foreach (var m in prop.SetMethods) + defsToShow.Add(m); + foreach (var m in prop.OtherMethods) + defsToShow.Add(m); + } + else { + var evt = method.DeclaringType.Events.FirstOrDefault(a => a.AddMethod == method || a.RemoveMethod == method); + if (evt is not null) { + defsToShow.Add(evt); + if (evt.AddMethod is not null) + defsToShow.Add(evt.AddMethod); + if (evt.RemoveMethod is not null) + defsToShow.Add(evt.RemoveMethod); + if (evt.InvokeMethod is not null) + defsToShow.Add(evt.InvokeMethod); + foreach (var m in evt.OtherMethods) + defsToShow.Add(m); + } + else + defsToShow.Add(method); + } + } + foreach (var type in types) { + if (!type.IsEnum) { + defsToShow.Add(type); + partialTypes.Add(type); + } + } + foreach (var def in defsToShow) { + for (var declType = def.DeclaringType; declType is not null; declType = declType.DeclaringType) + partialTypes.Add(declType); + } + foreach (var type in types) { + if (type.IsEnum) { + defsToShow.Add(type); + foreach (var f in type.Fields) + defsToShow.Add(f); + } + } + } + + public void Run(AstNode compilationUnit) { + foreach (var en in compilationUnit.Descendants.OfType()) { + var def = en.Annotation(); + Debug2.Assert(def is not null); + if (def is null) + continue; + + if (partialTypes.Contains(def)) { + var tdecl = en as TypeDeclaration; + Debug2.Assert(tdecl is not null); + if (tdecl is not null) { + if (tdecl.ClassType != ClassType.Enum) + tdecl.Modifiers |= Modifiers.Partial; + if (!showDefinitions) { + tdecl.BaseTypes.Clear(); + tdecl.Attributes.Clear(); + } + + // Make sure the comments are still shown before the method and its modifiers + var comments = en.GetChildrenByRole(Roles.Comment).Reverse().ToArray(); + foreach (var c in comments) { + c.Remove(); + en.InsertChildAfter(null, c, Roles.Comment); + } + } + } + else { + if (showDefinitions) { + if (!showAll && !defsToShow.Contains(def)) + en.Remove(); + } + else { + if (showAll || defsToShow.Contains(def)) + en.Remove(); + else if (en is CustomEventDeclaration ced) { + // Convert this hidden event to an event without accessor bodies. + // AstBuilder doesn't write empty bodies to it if it's a hidden event because + // then it can't be optimized to an auto event. We want real auto events to + // become auto events and custom events to stay custom, but without bodies. + if (!ced.AddAccessor.IsNull) + ced.AddAccessor.Body = new BlockStatement(); + if (!ced.RemoveAccessor.IsNull) + ced.RemoveAccessor.Body = new BlockStatement(); + } + } + } + } + } + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/CSharp/ThreadSafeObjectPool.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/CSharp/ThreadSafeObjectPool.cs new file mode 100644 index 0000000..254d223 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/CSharp/ThreadSafeObjectPool.cs @@ -0,0 +1,57 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; +using System.Collections.Generic; + +namespace dnSpy.Decompiler.ILSpy.Core.CSharp { + sealed class ThreadSafeObjectPool where T : class { + readonly List freeObjs; + readonly Func createObject; + readonly Action resetObject; + readonly object lockObj = new object(); + + public ThreadSafeObjectPool(int size, Func createObject, Action resetObject) { + if (size <= 0) + throw new ArgumentException(); + freeObjs = new List(size); + this.createObject = createObject; + this.resetObject = resetObject; + } + + public T Allocate() { + lock (lockObj) { + if (freeObjs.Count > 0) { + int i = freeObjs.Count - 1; + var o = freeObjs[i]; + freeObjs.RemoveAt(i); + return o; + } + + return createObject(); + } + } + + public void Free(T obj) { + resetObject(obj); + lock (lockObj) + freeObjs.Add(obj); + } + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/IL/ILDecompiler.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/IL/ILDecompiler.cs new file mode 100644 index 0000000..8a28c63 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/IL/ILDecompiler.cs @@ -0,0 +1,200 @@ +// 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 System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using dnlib.DotNet; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Decompiler.XmlDoc; +using dnSpy.Contracts.Text; +using dnSpy.Decompiler.IL; +using dnSpy.Decompiler.ILSpy.Core.Settings; +using dnSpy.Decompiler.ILSpy.Core.Text; +using dnSpy.Decompiler.ILSpy.Core.XmlDoc; +using ICSharpCode.Decompiler.Disassembler; + +namespace dnSpy.Decompiler.ILSpy.Core.IL { + sealed class DecompilerProvider : IDecompilerProvider { + readonly DecompilerSettingsService decompilerSettingsService; + + // Keep the default ctor. It's used by dnSpy.Console.exe + public DecompilerProvider() + : this(DecompilerSettingsService.__Instance_DONT_USE) { + } + + public DecompilerProvider(DecompilerSettingsService decompilerSettingsService) { + Debug2.Assert(decompilerSettingsService is not null); + this.decompilerSettingsService = decompilerSettingsService ?? throw new ArgumentNullException(nameof(decompilerSettingsService)); + } + + public IEnumerable Create() { + yield return new ILDecompiler(decompilerSettingsService.ILDecompilerSettings); + } + } + + /// + /// IL language support. + /// + /// + /// Currently comes in two versions: + /// flat IL (detectControlStructure=false) and structured IL (detectControlStructure=true). + /// + sealed class ILDecompiler : DecompilerBase { + readonly bool detectControlStructure; + + public override DecompilerSettingsBase Settings => langSettings; + readonly ILDecompilerSettings langSettings; + + public ILDecompiler(ILDecompilerSettings langSettings) + : this(langSettings, true) { + } + + public ILDecompiler(ILDecompilerSettings langSettings, bool detectControlStructure) { + this.langSettings = langSettings; + this.detectControlStructure = detectControlStructure; + } + + public override double OrderUI => DecompilerConstants.IL_ILSPY_ORDERUI; + public override string ContentTypeString => ContentTypesInternal.ILILSpy; + public override string GenericNameUI => DecompilerConstants.GENERIC_NAMEUI_IL; + public override string UniqueNameUI => "IL"; + public override Guid GenericGuid => DecompilerConstants.LANGUAGE_IL; + public override Guid UniqueGuid => DecompilerConstants.LANGUAGE_IL_ILSPY; + public override string FileExtension => ".il"; + + ReflectionDisassembler CreateReflectionDisassembler(IDecompilerOutput output, DecompilationContext ctx, IMemberDef member) => + CreateReflectionDisassembler(output, ctx, member.Module); + + ReflectionDisassembler CreateReflectionDisassembler(IDecompilerOutput output, DecompilationContext ctx, ModuleDef ownerModule) { + var disOpts = new DisassemblerOptions(langSettings.Settings.SettingsVersion, ctx.CancellationToken, ownerModule); + if (langSettings.Settings.ShowILComments) + disOpts.GetOpCodeDocumentation = ILLanguageHelper.GetOpCodeDocumentation; + var sb = new StringBuilder(); + if (langSettings.Settings.ShowXmlDocumentation) + disOpts.GetXmlDocComments = a => GetXmlDocComments(a, sb); + disOpts.CreateInstructionBytesReader = m => InstructionBytesReader.Create(m, ctx.IsBodyModified is not null && ctx.IsBodyModified(m)); + disOpts.ShowTokenAndRvaComments = langSettings.Settings.ShowTokenAndRvaComments; + disOpts.ShowILBytes = langSettings.Settings.ShowILBytes; + disOpts.SortMembers = langSettings.Settings.SortMembers; + disOpts.ShowPdbInfo = langSettings.Settings.ShowPdbInfo; + disOpts.MaxStringLength = langSettings.Settings.MaxStringLength; + disOpts.HexadecimalNumbers = langSettings.Settings.HexadecimalNumbers; + return new ReflectionDisassembler(output, detectControlStructure, disOpts); + } + + static IEnumerable GetXmlDocComments(IMemberRef mr, StringBuilder sb) { + if (mr is null || mr.Module is null) + yield break; + var xmldoc = XmlDocLoader.LoadDocumentation(mr.Module); + if (xmldoc is null) + yield break; + var doc = xmldoc.GetDocumentation(XmlDocKeyProvider.GetKey(mr, sb)); + if (string2.IsNullOrEmpty(doc)) + yield break; + + foreach (var info in new XmlDocLine(doc)) { + sb.Clear(); + if (info is not null) { + sb.Append(' '); + info.Value.WriteTo(sb); + } + yield return sb.ToString(); + } + } + + public override void Decompile(MethodDef method, IDecompilerOutput output, DecompilationContext ctx) { + var dis = CreateReflectionDisassembler(output, ctx, method); + dis.DisassembleMethod(method, true); + } + + public override void Decompile(FieldDef field, IDecompilerOutput output, DecompilationContext ctx) { + var dis = CreateReflectionDisassembler(output, ctx, field); + dis.DisassembleField(field, false); + } + + public override void Decompile(PropertyDef property, IDecompilerOutput output, DecompilationContext ctx) { + ReflectionDisassembler rd = CreateReflectionDisassembler(output, ctx, property); + rd.DisassembleProperty(property, addLineSep: true); + if (property.GetMethod is not null) { + output.WriteLine(); + rd.DisassembleMethod(property.GetMethod, true); + } + if (property.SetMethod is not null) { + output.WriteLine(); + rd.DisassembleMethod(property.SetMethod, true); + } + foreach (var m in property.OtherMethods) { + output.WriteLine(); + rd.DisassembleMethod(m, true); + } + } + + public override void Decompile(EventDef ev, IDecompilerOutput output, DecompilationContext ctx) { + ReflectionDisassembler rd = CreateReflectionDisassembler(output, ctx, ev); + rd.DisassembleEvent(ev, addLineSep: true); + if (ev.AddMethod is not null) { + output.WriteLine(); + rd.DisassembleMethod(ev.AddMethod, true); + } + if (ev.RemoveMethod is not null) { + output.WriteLine(); + rd.DisassembleMethod(ev.RemoveMethod, true); + } + foreach (var m in ev.OtherMethods) { + output.WriteLine(); + rd.DisassembleMethod(m, true); + } + } + + public override void Decompile(TypeDef type, IDecompilerOutput output, DecompilationContext ctx) { + var dis = CreateReflectionDisassembler(output, ctx, type); + dis.DisassembleType(type, true); + } + + public override void Decompile(AssemblyDef asm, IDecompilerOutput output, DecompilationContext ctx) { + output.WriteLine("// " + asm.ManifestModule.Location, BoxedTextColor.Comment); + PrintEntryPoint(asm.ManifestModule, output); + output.WriteLine(); + + ReflectionDisassembler rd = CreateReflectionDisassembler(output, ctx, asm.ManifestModule); + rd.WriteAssemblyHeader(asm); + } + + public override void Decompile(ModuleDef mod, IDecompilerOutput output, DecompilationContext ctx) { + output.WriteLine("// " + mod.Location, BoxedTextColor.Comment); + PrintEntryPoint(mod, output); + output.WriteLine(); + + ReflectionDisassembler rd = CreateReflectionDisassembler(output, ctx, mod); + output.WriteLine(); + rd.WriteModuleHeader(mod); + } + + protected override void TypeToString(IDecompilerOutput output, ITypeDefOrRef? t, bool includeNamespace, IHasCustomAttribute? attributeProvider = null) => + t.WriteTo(output, includeNamespace ? ILNameSyntax.TypeName : ILNameSyntax.ShortTypeName); + + public override void WriteToolTip(ITextColorWriter output, IMemberRef member, IHasCustomAttribute? typeAttributes) { + if (!(member is ITypeDefOrRef) && ILDecompilerUtils.Write(TextColorWriterToDecompilerOutput.Create(output), member)) + return; + + base.WriteToolTip(output, member, typeAttributes); + } + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/IL/ILDecompilerUtils.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/IL/ILDecompilerUtils.cs new file mode 100644 index 0000000..9ecd4bf --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/IL/ILDecompilerUtils.cs @@ -0,0 +1,57 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using dnlib.DotNet; +using dnSpy.Contracts.Decompiler; +using ICSharpCode.Decompiler.Disassembler; + +namespace dnSpy.Decompiler.ILSpy.Core.IL { + static class ILDecompilerUtils { + public static bool Write(IDecompilerOutput output, IMemberRef? member) { + if (member is IMethod method && method.IsMethod) { + method.WriteMethodTo(output); + return true; + } + + if (member is IField field && field.IsField) { + field.WriteFieldTo(output); + return true; + } + + if (member is PropertyDef prop) { + var dis = new ReflectionDisassembler(output, false, new DisassemblerOptions(0, new System.Threading.CancellationToken(), null)); + dis.DisassembleProperty(prop, false); + return true; + } + + if (member is EventDef evt) { + var dis = new ReflectionDisassembler(output, false, new DisassemblerOptions(0, new System.Threading.CancellationToken(), null)); + dis.DisassembleEvent(evt, false); + return true; + } + + if (member is ITypeDefOrRef type) { + type.WriteTo(output, ILNameSyntax.TypeName); + return true; + } + + return false; + } + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/ILAst/ILAstDecompiler.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/ILAst/ILAstDecompiler.cs new file mode 100644 index 0000000..66e505c --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/ILAst/ILAstDecompiler.cs @@ -0,0 +1,386 @@ +// 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 System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using dnlib.DotNet; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Text; +using dnSpy.Decompiler.ILSpy.Core.Settings; +using dnSpy.Decompiler.ILSpy.Core.Text; +using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.Disassembler; +using ICSharpCode.Decompiler.ILAst; + +namespace dnSpy.Decompiler.ILSpy.Core.ILAst { + sealed class DecompilerProvider : IDecompilerProvider { + readonly DecompilerSettingsService decompilerSettingsService; + + // Keep the default ctor. It's used by dnSpy.Console.exe + public DecompilerProvider() + : this(DecompilerSettingsService.__Instance_DONT_USE) { + } + + public DecompilerProvider(DecompilerSettingsService decompilerSettingsService) { + Debug2.Assert(decompilerSettingsService is not null); + this.decompilerSettingsService = decompilerSettingsService ?? throw new ArgumentNullException(nameof(decompilerSettingsService)); + } + + public IEnumerable Create() { +#if DEBUG + foreach (var l in ILAstDecompiler.GetDebugDecompilers(decompilerSettingsService)) + yield return l; +#endif + yield break; + } + } + +#if DEBUG + /// + /// Represents the ILAst "language" used for debugging purposes. + /// + sealed class ILAstDecompiler : DecompilerBase { + readonly string uniqueNameUI; + Guid uniqueGuid; + bool inlineVariables = true; + ILAstOptimizationStep? abortBeforeStep; + + public override DecompilerSettingsBase Settings { get; } + const int settingsVersion = 1; + + ILAstDecompiler(ILAstDecompilerSettings langSettings, double orderUI, string uniqueNameUI) { + Settings = langSettings; + OrderUI = orderUI; + this.uniqueNameUI = uniqueNameUI; + } + + public override double OrderUI { get; } + public override string ContentTypeString => ContentTypesInternal.ILAstILSpy; + public override string GenericNameUI => "ILAst"; + public override string UniqueNameUI => uniqueNameUI; + public override Guid GenericGuid => DecompilerConstants.LANGUAGE_ILAST_ILSPY; + public override Guid UniqueGuid => uniqueGuid; + + public override void Decompile(MethodDef method, IDecompilerOutput output, DecompilationContext ctx) { + WriteCommentBegin(output, true); + output.Write("Method: ", BoxedTextColor.Comment); + output.Write(IdentifierEscaper.Escape(method.FullName), method, DecompilerReferenceFlags.Definition, BoxedTextColor.Comment); + WriteCommentEnd(output, true); + output.WriteLine(); + + if (!method.HasBody) { + return; + } + + var bodyInfo = StartKeywordBlock(output, ".body", method); + + ILAstBuilder astBuilder = new ILAstBuilder(); + ILBlock ilMethod = new ILBlock(CodeBracesRangeFlags.MethodBraces); + DecompilerContext context = new DecompilerContext(settingsVersion, method.Module, MetadataTextColorProvider) { + CurrentType = method.DeclaringType, + CurrentMethod = method, + CalculateILSpans = ctx.CalculateILSpans, + }; + ilMethod.Body = astBuilder.Build(method, inlineVariables, context); + + var stateMachineKind = StateMachineKind.None; + MethodDef? inlinedMethod = null; + AsyncMethodDebugInfo? asyncInfo = null; + string? compilerName = null; + if (abortBeforeStep is not null) { + var optimizer = new ILAstOptimizer(); + optimizer.Optimize(context, ilMethod, out stateMachineKind, out inlinedMethod, out asyncInfo, abortBeforeStep.Value); + compilerName = optimizer.CompilerName; + } + + if (context.CurrentMethodIsYieldReturn) { + output.Write("yield", BoxedTextColor.Keyword); + output.Write(" ", BoxedTextColor.Text); + output.WriteLine("return", BoxedTextColor.Keyword); + } + if (context.CurrentMethodIsAsync) { + output.Write("async", BoxedTextColor.Keyword); + output.Write("/", BoxedTextColor.Punctuation); + output.WriteLine("await", BoxedTextColor.Keyword); + } + + var allVariables = ilMethod.GetSelfAndChildrenRecursive().Select(e => e.Operand as ILVariable) + .Where(v => v is not null && !v.IsParameter).Distinct(); + foreach (var v in allVariables) { + Debug2.Assert(v is not null); + output.Write(IdentifierEscaper.Escape(v.Name), v.GetTextReferenceObject(), DecompilerReferenceFlags.Local | DecompilerReferenceFlags.Definition, v.IsParameter ? BoxedTextColor.Parameter : BoxedTextColor.Local); + if (v.Type is not null) { + output.Write(" ", BoxedTextColor.Text); + output.Write(":", BoxedTextColor.Punctuation); + output.Write(" ", BoxedTextColor.Text); + if (v.IsPinned) { + output.Write("pinned", BoxedTextColor.Keyword); + output.Write(" ", BoxedTextColor.Text); + } + v.Type.WriteTo(output, ILNameSyntax.ShortTypeName); + } + if (v.GeneratedByDecompiler) { + output.Write(" ", BoxedTextColor.Text); + var start = output.NextPosition; + output.Write("[", BoxedTextColor.Punctuation); + output.Write("generated", BoxedTextColor.Keyword); + var end = output.NextPosition; + output.Write("]", BoxedTextColor.Punctuation); + output.AddBracePair(new TextSpan(start, 1), new TextSpan(end, 1), CodeBracesRangeFlags.SquareBrackets); + } + output.WriteLine(); + } + + var localVariables = new HashSet(GetVariables(ilMethod)); + var builder = new MethodDebugInfoBuilder(settingsVersion, stateMachineKind, inlinedMethod ?? method, inlinedMethod is not null ? method : null, CreateSourceLocals(localVariables), CreateSourceParameters(localVariables), asyncInfo); + builder.CompilerName = compilerName; + foreach (ILNode node in ilMethod.Body) { + node.WriteTo(output, builder); + if (!node.WritesNewLine) + output.WriteLine(); + } + output.AddDebugInfo(builder.Create()); + EndKeywordBlock(output, bodyInfo, CodeBracesRangeFlags.MethodBraces, addLineSeparator: true); + } + + IEnumerable GetVariables(ILBlock ilMethod) { + foreach (var n in ilMethod.GetSelfAndChildrenRecursive(new List())) { + var expr = n as ILExpression; + if (expr is not null) { + var v = expr.Operand as ILVariable; + if (v is not null) + yield return v; + continue; + } + var cb = n as ILTryCatchBlock.CatchBlockBase; + if (cb is not null && cb.ExceptionVariable is not null) + yield return cb.ExceptionVariable; + } + } + + readonly List sourceLocalsList = new List(); + SourceLocal[] CreateSourceLocals(HashSet variables) { + foreach (var v in variables) { + if (v.IsParameter) + continue; + sourceLocalsList.Add(v.GetSourceLocal()); + } + var array = sourceLocalsList.ToArray(); + sourceLocalsList.Clear(); + return array; + } + + readonly List sourceParametersList = new List(); + SourceParameter[] CreateSourceParameters(HashSet variables) { + foreach (var v in variables) { + if (!v.IsParameter) + continue; + sourceParametersList.Add(v.GetSourceParameter()); + } + var array = sourceParametersList.ToArray(); + sourceParametersList.Clear(); + return array; + } + + struct BraceInfo { + public int Start { get; } + public BraceInfo(int start) => Start = start; + } + + BraceInfo StartKeywordBlock(IDecompilerOutput output, string keyword, IMemberDef member) { + output.Write(keyword, BoxedTextColor.Keyword); + output.Write(" ", BoxedTextColor.Text); + output.Write(IdentifierEscaper.Escape(member.Name), member, DecompilerReferenceFlags.Definition, MetadataTextColorProvider.GetColor(member)); + output.Write(" ", BoxedTextColor.Text); + var start = output.NextPosition; + output.Write("{", BoxedTextColor.Punctuation); + output.WriteLine(); + output.IncreaseIndent(); + return new BraceInfo(start); + } + + void EndKeywordBlock(IDecompilerOutput output, BraceInfo info, CodeBracesRangeFlags flags, bool addLineSeparator = false) { + output.DecreaseIndent(); + var end = output.NextPosition; + output.Write("}", BoxedTextColor.Punctuation); + output.AddBracePair(new TextSpan(info.Start, 1), new TextSpan(end, 1), flags); + if (addLineSeparator) + output.AddLineSeparator(end); + output.WriteLine(); + } + + public override void Decompile(EventDef ev, IDecompilerOutput output, DecompilationContext ctx) { + var eventInfo = StartKeywordBlock(output, ".event", ev); + + if (ev.AddMethod is not null) { + var info = StartKeywordBlock(output, ".add", ev.AddMethod); + EndKeywordBlock(output, info, CodeBracesRangeFlags.AccessorBraces); + } + + if (ev.InvokeMethod is not null) { + var info = StartKeywordBlock(output, ".invoke", ev.InvokeMethod); + EndKeywordBlock(output, info, CodeBracesRangeFlags.AccessorBraces); + } + + if (ev.RemoveMethod is not null) { + var info = StartKeywordBlock(output, ".remove", ev.RemoveMethod); + EndKeywordBlock(output, info, CodeBracesRangeFlags.AccessorBraces); + } + + EndKeywordBlock(output, eventInfo, CodeBracesRangeFlags.EventBraces, addLineSeparator: true); + } + + public override void Decompile(FieldDef field, IDecompilerOutput output, DecompilationContext ctx) { + output.Write(IdentifierEscaper.Escape(field.FieldType.GetFullName()), field.FieldType.ToTypeDefOrRef(), DecompilerReferenceFlags.None, MetadataTextColorProvider.GetColor(field.FieldType)); + output.Write(" ", BoxedTextColor.Text); + output.Write(IdentifierEscaper.Escape(field.Name), field, DecompilerReferenceFlags.Definition, MetadataTextColorProvider.GetColor(field)); + var c = field.Constant; + if (c is not null) { + output.Write(" ", BoxedTextColor.Text); + output.Write("=", BoxedTextColor.Operator); + output.Write(" ", BoxedTextColor.Text); + if (c.Value is null) + output.Write("null", BoxedTextColor.Keyword); + else { + switch (c.Type) { + case ElementType.Boolean: + if (c.Value is bool) + output.Write((bool)c.Value ? "true" : "false", BoxedTextColor.Keyword); + else + goto default; + break; + + case ElementType.Char: + output.Write($"'{c.Value}'", BoxedTextColor.Char); + break; + + case ElementType.I1: + case ElementType.U1: + case ElementType.I2: + case ElementType.U2: + case ElementType.I4: + case ElementType.U4: + case ElementType.I8: + case ElementType.U8: + case ElementType.R4: + case ElementType.R8: + case ElementType.I: + case ElementType.U: + output.Write($"{c.Value}", BoxedTextColor.Number); + break; + + case ElementType.String: + output.Write($"{c.Value}", BoxedTextColor.String); + break; + + default: + output.Write($"{c.Value}", BoxedTextColor.Text); + break; + } + } + } + } + + public override void Decompile(PropertyDef property, IDecompilerOutput output, DecompilationContext ctx) { + var propInfo = StartKeywordBlock(output, ".property", property); + + foreach (var getter in property.GetMethods) { + var info = StartKeywordBlock(output, ".get", getter); + EndKeywordBlock(output, info, CodeBracesRangeFlags.AccessorBraces); + } + + foreach (var setter in property.SetMethods) { + var info = StartKeywordBlock(output, ".set", setter); + EndKeywordBlock(output, info, CodeBracesRangeFlags.AccessorBraces); + } + + foreach (var other in property.OtherMethods) { + var info = StartKeywordBlock(output, ".other", other); + EndKeywordBlock(output, info, CodeBracesRangeFlags.AccessorBraces); + } + + EndKeywordBlock(output, propInfo, CodeBracesRangeFlags.PropertyBraces, addLineSeparator: true); + } + + public override void Decompile(TypeDef type, IDecompilerOutput output, DecompilationContext ctx) { + this.WriteCommentLine(output, $"Type: {type.FullName}"); + if (type.BaseType is not null) { + WriteCommentBegin(output, true); + output.Write("Base type: ", BoxedTextColor.Comment); + output.Write(IdentifierEscaper.Escape(type.BaseType.FullName), type.BaseType, DecompilerReferenceFlags.None, BoxedTextColor.Comment); + WriteCommentEnd(output, true); + output.WriteLine(); + } + foreach (var nested in type.NestedTypes) { + Decompile(nested, output, ctx); + output.WriteLine(); + } + + int lastFieldPos = -1; + foreach (var field in type.Fields) { + Decompile(field, output, ctx); + lastFieldPos = output.NextPosition; + output.WriteLine(); + } + if (lastFieldPos >= 0) { + output.AddLineSeparator(lastFieldPos); + output.WriteLine(); + } + + foreach (var property in type.Properties) { + Decompile(property, output, ctx); + output.WriteLine(); + } + + foreach (var @event in type.Events) { + Decompile(@event, output, ctx); + output.WriteLine(); + } + + foreach (var method in type.Methods) { + Decompile(method, output, ctx); + output.WriteLine(); + } + } + + internal static IEnumerable GetDebugDecompilers(DecompilerSettingsService decompilerSettingsService) { + double orderUI = DecompilerConstants.ILAST_ILSPY_DEBUG_ORDERUI; + uint id = 0x64A926A5; + yield return new ILAstDecompiler(decompilerSettingsService.ILAstDecompilerSettings, orderUI++, "ILAst (unoptimized)") { + uniqueGuid = new Guid($"CB470049-6AFB-4BDB-93DC-1BB9{id++:X8}"), + inlineVariables = false + }; + string nextName = "ILAst (variable splitting)"; + foreach (ILAstOptimizationStep step in (ILAstOptimizationStep[])Enum.GetValues(typeof(ILAstOptimizationStep))) { + yield return new ILAstDecompiler(decompilerSettingsService.ILAstDecompilerSettings, orderUI++, nextName) { + uniqueGuid = new Guid($"CB470049-6AFB-4BDB-93DC-1BB9{id++:X8}"), + abortBeforeStep = step + }; + nextName = "ILAst (after " + step + ")"; + } + } + + public override string FileExtension => ".il"; + + protected override void TypeToString(IDecompilerOutput output, ITypeDefOrRef? t, bool includeNamespace, IHasCustomAttribute? attributeProvider = null) => + t.WriteTo(output, includeNamespace ? ILNameSyntax.TypeName : ILNameSyntax.ShortTypeName); + } +#endif +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/AssemblyInfo.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..5df10df --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("dnSpy.Decompiler.ILSpy.x, PublicKey=0024000004800000940000000602000000240000525341310004000001000100858d4f2519af95faeca8d359daa2078b20826765450f330daff3c5ec378adf9eb8e168eff8069ba51e15c1992d0d72b73129ed7a014f62863fa80a80dd9b010b8211c4d4c70ed26b9b65370007b0341685c98cc4cd2d16814d9581db82b382fb8ee838e1b8a2bd1c759e43ddda209e35cde530a7ba6a517787c04a3f283ea1b5")] diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.Designer.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.Designer.cs new file mode 100644 index 0000000..fe0b207 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.Designer.cs @@ -0,0 +1,415 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace dnSpy.Decompiler.ILSpy.Core.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class dnSpy_Decompiler_ILSpy_Core_Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal dnSpy_Decompiler_ILSpy_Core_Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("dnSpy.Decompiler.ILSpy.Core.Properties.dnSpy.Decompiler.ILSpy.Core.Resources", typeof(dnSpy_Decompiler_ILSpy_Core_Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Add using declarations. + /// + public static string DecompilerSettings_AddUsingDeclarations { + get { + return ResourceManager.GetString("DecompilerSettings_AddUsingDeclarations", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Allow field initializers. + /// + public static string DecompilerSettings_AllowFieldInitializers { + get { + return ResourceManager.GetString("DecompilerSettings_AllowFieldInitializers", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Always generate exception variables in catch blocks unless type is Object. + /// + public static string DecompilerSettings_AlwaysGenerateExceptionVariableForCatchBlocksUnlessTypeIsObject { + get { + return ResourceManager.GetString("DecompilerSettings_AlwaysGenerateExceptionVariableForCatchBlocksUnlessTypeIsObjec" + + "t", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Decompilation order. + /// + public static string DecompilerSettings_DecompilationOrder { + get { + return ResourceManager.GetString("DecompilerSettings_DecompilationOrder", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Decompile anonymous methods/lambdas. + /// + public static string DecompilerSettings_DecompileAnonMethods { + get { + return ResourceManager.GetString("DecompilerSettings_DecompileAnonMethods", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Decompile async methods (async/await). + /// + public static string DecompilerSettings_DecompileAsyncMethods { + get { + return ResourceManager.GetString("DecompilerSettings_DecompileAsyncMethods", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Decompile automatic events. + /// + public static string DecompilerSettings_DecompileAutoEvents { + get { + return ResourceManager.GetString("DecompilerSettings_DecompileAutoEvents", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Decompile automatic properties. + /// + public static string DecompilerSettings_DecompileAutoProps { + get { + return ResourceManager.GetString("DecompilerSettings_DecompileAutoProps", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Decompile enumerators (yield return). + /// + public static string DecompilerSettings_DecompileEnumerators { + get { + return ResourceManager.GetString("DecompilerSettings_DecompileEnumerators", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Decompile expression trees. + /// + public static string DecompilerSettings_DecompileExprTrees { + get { + return ResourceManager.GetString("DecompilerSettings_DecompileExprTrees", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Decompile foreach statements. + /// + public static string DecompilerSettings_DecompileForeachStatements { + get { + return ResourceManager.GetString("DecompilerSettings_DecompileForeachStatements", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Decompile lock statements. + /// + public static string DecompilerSettings_DecompileLockStatements { + get { + return ResourceManager.GetString("DecompilerSettings_DecompileLockStatements", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Decompile query expressions. + /// + public static string DecompilerSettings_DecompileQueryExpr { + get { + return ResourceManager.GetString("DecompilerSettings_DecompileQueryExpr", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Decompile switch on string. + /// + public static string DecompilerSettings_DecompileSwitchOnString { + get { + return ResourceManager.GetString("DecompilerSettings_DecompileSwitchOnString", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Decompile using statements. + /// + public static string DecompilerSettings_DecompileUsingStatements { + get { + return ResourceManager.GetString("DecompilerSettings_DecompileUsingStatements", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Add namespaces to all types. + /// + public static string DecompilerSettings_FullyQualifyAllTypes { + get { + return ResourceManager.GetString("DecompilerSettings_FullyQualifyAllTypes", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Add a namespace to types with the same name. + /// + public static string DecompilerSettings_FullyQualifyAmbiguousTypeNames { + get { + return ResourceManager.GetString("DecompilerSettings_FullyQualifyAmbiguousTypeNames", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Hexadecimal numbers. + /// + public static string DecompilerSettings_HexadecimalNumbers { + get { + return ResourceManager.GetString("DecompilerSettings_HexadecimalNumbers", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use increment and decrement operators. + /// + public static string DecompilerSettings_IntroduceIncrementAndDecrement { + get { + return ResourceManager.GetString("DecompilerSettings_IntroduceIncrementAndDecrement", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use assignment expressions such as in while ((count = Do()) != 0) ;. + /// + public static string DecompilerSettings_MakeAssignmentExpressions { + get { + return ResourceManager.GetString("DecompilerSettings_MakeAssignmentExpressions", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Max number of array elements to show. + /// + public static string DecompilerSettings_MaxArrayElements { + get { + return ResourceManager.GetString("DecompilerSettings_MaxArrayElements", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Max string length. + /// + public static string DecompilerSettings_MaxStringLength { + get { + return ResourceManager.GetString("DecompilerSettings_MaxStringLength", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Add 'private' modifier to type members. + /// + public static string DecompilerSettings_MemberAddPrivateModifier { + get { + return ResourceManager.GetString("DecompilerSettings_MemberAddPrivateModifier", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Decompile object or collection initializers. + /// + public static string DecompilerSettings_ObjectOrCollectionInitializers { + get { + return ResourceManager.GetString("DecompilerSettings_ObjectOrCollectionInitializers", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show one custom attribute per line. + /// + public static string DecompilerSettings_OneCustomAttributePerLine { + get { + return ResourceManager.GetString("DecompilerSettings_OneCustomAttributePerLine", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Remove empty default constructors. + /// + public static string DecompilerSettings_RemoveEmptyDefaultCtors { + get { + return ResourceManager.GetString("DecompilerSettings_RemoveEmptyDefaultCtors", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Replace 'new delegate-class(xxx)' with 'xxx'. + /// + public static string DecompilerSettings_RemoveNewDelegateClass { + get { + return ResourceManager.GetString("DecompilerSettings_RemoveNewDelegateClass", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show hidden compiler generated types and methods. + /// + public static string DecompilerSettings_ShowCompilerGeneratedTypes { + get { + return ResourceManager.GetString("DecompilerSettings_ShowCompilerGeneratedTypes", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show IL opcode comments. + /// + public static string DecompilerSettings_ShowILComments { + get { + return ResourceManager.GetString("DecompilerSettings_ShowILComments", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show IL instruction bytes. + /// + public static string DecompilerSettings_ShowILInstrBytes { + get { + return ResourceManager.GetString("DecompilerSettings_ShowILInstrBytes", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show line numbers and filenames if available. + /// + public static string DecompilerSettings_ShowPdbInfo { + get { + return ResourceManager.GetString("DecompilerSettings_ShowPdbInfo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show tokens, RVAs and file offsets. + /// + public static string DecompilerSettings_ShowTokensRvasOffsets { + get { + return ResourceManager.GetString("DecompilerSettings_ShowTokensRvasOffsets", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show XML documentation in decompiled code. + /// + public static string DecompilerSettings_ShowXMLDocComments { + get { + return ResourceManager.GetString("DecompilerSettings_ShowXMLDocComments", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sort custom attributes. + /// + public static string DecompilerSettings_SortCustomAttributes { + get { + return ResourceManager.GetString("DecompilerSettings_SortCustomAttributes", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sort methods, fields, properties, events and types. + /// + public static string DecompilerSettings_SortMethods { + get { + return ResourceManager.GetString("DecompilerSettings_SortMethods", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Place 'System' directives first when sorting usings. + /// + public static string DecompilerSettings_SortSystemFirst { + get { + return ResourceManager.GetString("DecompilerSettings_SortSystemFirst", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Add 'internal' modifier to types. + /// + public static string DecompilerSettings_TypeAddInternalModifier { + get { + return ResourceManager.GetString("DecompilerSettings_TypeAddInternalModifier", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use variable names from debug symbols, if available. + /// + public static string DecompilerSettings_UseLocalNameFromSyms { + get { + return ResourceManager.GetString("DecompilerSettings_UseLocalNameFromSyms", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Order members in source code order. + /// + public static string DecompilerSettings_UseSourceCodeOrder { + get { + return ResourceManager.GetString("DecompilerSettings_UseSourceCodeOrder", resourceCulture); + } + } + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.cs.resx b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.cs.resx new file mode 100644 index 0000000..1ecd212 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.cs.resx @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Přidat deklaraci using + + + Přidat inicializátory pole + + + Vždy generovat proměnné výjimek v blocích catch, dokud je typ objekt + + + Pořadí dekompilace + + + Dekompilovat anonymní metody/lambda výrazy + + + Dekompilovat asynchronní metody (async/await) + + + Dekompilovat automatické události + + + Dekompilovat automatické vlastnosti + + + Dekompilovat enumerátory (yield return) + + + Dekompilovat stromy výrazů + + + Dekompilovat foreach konstrukce + + + Dekompilovat lock deklarace + + + Dekompilovat query výrazy + + + Dekompilovat spínač na řetězec + + + Dekompilovat prohlášení using + + + Přidat jmenné prostory pro všechny typy + + + Přidat jmenné prostory pro typy se stejným názvem + + + Hexadecimal numbers + + + Použít operátory zvyšování a snižování + + + Použít výrazy přiřazení jako například v: while ((count = Do()) != 0) ; + + + Maximální počet zobrazených prvků pole + + + Maximální délka řetězce + + + Přidat 'private' modifikátor pro členy typu + + + Dekompilovat objekt nebo iniciátory kolekce + + + Zobrazit jeden vlastní atribut na řádek + + + Odstranit prázdné výchozí konstruktory + + + Nahradit 'new delegate-class(xxx)' slovy 'xxx' + + + Zobrazit skryté typy a metody generované kompilátorem + + + Zobrazit komentáře opkódů IL + + + Zobrazit bajty IL instrukce + + + Zobrazit čísla řádku a jména souborů, pokud jsou k dispozici + + + Zobrazit tokeny, RVA a ofsety + + + Zobrazit XML dokumentaci v dekompilovaném kódu + + + Seřadit vlastní atributy + + + Seřadit metody, pole, vlastnosti, události a typy + + + Umístit direktivy 'System' jako první při třídění + + + Přidat na typy modifikátor 'internal' + + + Použít názvy proměnných ze symbolů ladění, pokud jsou k dispozici + + + Uspořádat členy v pořadí zdrojového kódu + + \ No newline at end of file diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.de.resx b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.de.resx new file mode 100644 index 0000000..30d0301 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.de.resx @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Using Deklarationen hinzufügen + + + Felder initialisieren + + + Immer Variable für Ausnahme im catch-Block generieren außer der Typ ist Object + + + Dekompilierungsreihenfolge + + + Anonyme Methoden/Lambda-Ausdrücke dekompilieren + + + Async-Methoden (async/await) dekompilieren + + + Automatisch implementierte Ereignisse dekompilieren + + + Automatisch implementierte Eigenschaften dekompilieren + + + Enumeratoren (yield return) dekompilieren + + + Ausdrucksbaumstrukturen dekompilieren + + + foreach-Anweisungen dekompilieren + + + lock-Anweisungen dekompilieren + + + Abfrageausdrücke dekompilieren + + + Switch-Anweisungen mit Zeichenfolgen dekompilieren + + + using-Anweisungen dekompilieren + + + Allen Typen einen Namespace hinzufügen + + + Typen mit dem gleichen Namen einen Namespace hinzufügen + + + Hexadezimale Zahlen + + + Inkrement- und Dekrementoperatoren verwenden + + + Verwende Zuweisungsausdrücke wie in while ((count = Do()) != 0); + + + Maximale Anzahl an anzuzeigenden Array-Elementen + + + Maximale Zeichenkettenlänge + + + Modifizierer 'private' zu Typmembern hinzufügen + + + Dekompiliere Objekt oder Auflistung Initialisierungen + + + Ein Attribut pro Zeile anzeigen + + + Leere Standardkonstruktoren entfernen + + + 'new delegate-class(xxx)' durch 'xxx' ersetzen + + + Versteckte Compiler generierte Typen und Methoden anzeigen + + + IL-Opcode Kommentare anzeigen + + + Bytes der IL Instruktion anzeigen + + + Dateinamen und Zeilennummern bei Verfügbarkeit anzeigen + + + Token, RVAs und Datei-Offsets anzeigen + + + XML-Dokumentation in dekompiliertem Code anzeigen + + + Benutzerdefinierte Attribute sortieren + + + Methoden, Felder, Eigenschaften, Ereignisse und Typen sortieren + + + "System"-Direktiven beim Sortieren von using-Direktiven zuerst anordnen + + + Modifizierer 'internal' zu Typen hinzufügen + + + Variablennamen von Debugsymbolen verwenden, falls vorhanden + + + Member wie im Quellcode anordnen + + \ No newline at end of file diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.es-ES.resx b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.es-ES.resx new file mode 100644 index 0000000..621312b --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.es-ES.resx @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Añadir declaraciones using + + + Permiten a inicializadores de campo + + + Genere siempre variables de excepción en bloques catch a menos que el tipo sea Object + + + Orden de descompilación + + + Descompilar métodos anónimos/lambdas + + + Descompilar métodos asincrónicos (async/await) + + + Descompilar eventos automáticamente + + + Descompilar propiedades automáticamente + + + Descompilar enumeradores (retorno yield) + + + Descompilar árboles de expresión + + + Descompilar declaraciones foreach + + + Descompilar declaraciones de bloqueo + + + Descompilar expresiones de consulta + + + Descompilar interruptor de cadena + + + Descompilar sentencias using + + + Agregar espacios de nombre a todos los tipos + + + Añadir un espacio de nombre a los tipos con el mismo nombre + + + Números hexadecimales + + + Uso de incremento y decremento de operadores + + + Asignación de expresiones tales como while ((Count = Do())! = 0); + + + Número máximo de elementos de matriz para mostrar + + + Longitud máxima de cadena + + + Añadir el modificador 'private' para miembros type + + + Descompilar los inicializadores de objeto o colección + + + Mostrar un atributo personalizado por línea + + + Quitar los constructores predeterminados de vacío + + + Reemplazar 'delegate-class(xxx) nuevo' con 'xxx' + + + Mostrar los tipos y métodos ocultos generados por el compilador + + + Mostrar los comentarios de opcode IL + + + Mostrar las instrucciones bytes IL + + + Mostrar números de línea y nombres de archivos si están disponibles + + + Mostrar fichas, RVAs y compensaciones de archivo + + + Mostrar la documentación XML en código descompilado + + + Ordenar atributos personalizados + + + Ordenar los métodos, campos, propiedades, eventos y tipos + + + Colocar la directiva 'using System' en primer al ordenar los using´s + + + Agregue el modificador 'internal' a tipos + + + Utilice nombres de variables de símbolos de depuración, si está disponible + + + Ordene miembros en orden de código fuente + + \ No newline at end of file diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.fa.resx b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.fa.resx new file mode 100644 index 0000000..9063982 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.fa.resx @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + اضافه کردن با استفاده از تعاریف + + + اجازه دادن به فیلد آغازگر + + + همیشه متغیرهای exception را در بلاک های catch ایجادکن مگر اینکه نوع Object باشد + + + ترتیب دیکامپایل + + + دیکامپایل method/lambda های ناشناس + + + دیکامپایل متودهای ناهمگام (async/await) + + + دیکامپایل event های خودکار + + + دیکامپایل خواص خودکار + + + دیکامپایل شمارنده ها (بازگشت yield) + + + دیکامپایل درخت های expression + + + دیکامپایل foreach statements + + + دیکامپایل lock statements + + + دیکامپایل query expressions + + + دیکامپایل سوئیچ در رشته + + + دیکامپایل با استفاده از statement ها + + + اضافه کردن فضاهای نام به همه تایپ ها + + + اضافه کردن فضای نام به انواع با نام مشابه + + + اعداد هگز + + + استفاده از اپراتورهای افزایشی و کاهشی + + + استفاده از انتصاب اصطلاحات مانند استفاده در while ها ((count = Do()) != 0) ; + + + حداکثر تعداد عناصر آرایه برای نشان دادن + + + حداکثر طول رشته + + + اضافه کردن اصلاح کننده 'خصوصی'به اعضای تایپ + + + دیکامپایل آبجکت یا مجموعه ی آغازگرها + + + نمایش یک attribute سفارشی شده در هر خط + + + حذف constructor های خالی پیشفرض + + + جایگزینی 'new delegate-class(xxx)' با 'xxx' + + + نمایش متودها و تایپ های مخفی تولید شده توسط دیکامپایلر + + + نمایش توضیحات IL opcode + + + نمایش بایت های دستورات IL + + + نمایش شماره خط و نام فایل در صورت وجود + + + نشان دادن token ها، RVA ها و آفست های فایل + + + نمایش مستندات XML در کد های دیکامپایل شده + + + مرتب کردن attribute های سفارشی + + + مرتب کردن method ها، field ها، propertie ها، event ها و type ها + + + دستورالعمل های 'System' را هنگام مرتب سازی using ها ابتدا قرار بده + + + اضافه کردن اصلاح کننده "داخلى" به تایپ ها + + + استفاده از نام متغیرها از سیمبول های دیباگ، در صورت وجود + + + منظم کردن اعضا در ترتیب سورس کد + + \ No newline at end of file diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.fr.resx b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.fr.resx new file mode 100644 index 0000000..e709bd9 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.fr.resx @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Ajouter les déclarations "using" + + + Autoriser les "initializers" de champs + + + Toujours générer des variables d’exception dans les blocs "catch" sauf si le type est Object + + + Ordre de décompilation + + + Décompiler les méthodes anonymes/lambda + + + Décompiler des méthodes asynchrones (async/await) + + + Décompiler les events automatiques + + + Décompiler les propriétés automatiques + + + Décompiler les énumérateurs (yield) + + + Décompiler les arbres d'expressions + + + Décompiler les "foreach" + + + Décompiler les "lock" + + + Décompiler les expressions de requêtes + + + Décompiler le switch sur string + + + Décompiler les instructions "using" + + + Ajouter des espaces de noms à tous les types + + + Ajouter un espace de noms aux types portant le même nom + + + Nombres hexadécimaux + + + Utiliser les opérateurs incrémentaux/décrémentaux + + + Utiliser des expressions d’assignation comme dans while((c = Executer()) !=0); + + + Nombre maximal d’éléments de tableau à afficher + + + Longueur de chaîne maximale + + + Ajoutez le modificateur « privé » pour les membres de type + + + Décompiler les initialiseurs d’objets ou de collections + + + Afficher un attribut personnalisé par ligne + + + Supprimer les constructeurs par défaut vides + + + Remplacer « new delegate-class(xxx) » par « xxx » + + + Afficher les types et méthodes générées cachées par le compilateur + + + Afficher les commentaires d'opcode IL + + + Afficher les bytes des instructions de l'IL + + + Afficher les numéros de lignes et noms de fichiers si disponibles + + + Afficher les tokens, RVAs et offsets dans les fichiers + + + Afficher la documentation XML dans le code décompilé + + + Trier les attributs personnalisés + + + Trier les méthodes, champs, propriétés, events et types + + + Priorité donnée aux "system" lors du tri des "using" + + + Ajoutez le modificateur « interne » aux types + + + Utilisez des noms de variables de symboles de débogage, si disponible + + + Membres triés selon l’ordre du code source + + \ No newline at end of file diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.hu.resx b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.hu.resx new file mode 100644 index 0000000..e363558 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.hu.resx @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + using deklarciók hozzáadása + + + Mező inicializálók engedélyezése + + + Mindig generáljon kivétel változókat az elkapó blokkban, kivéve ha a típus Object + + + Visszafordítási sorrend + + + Anoním metódusok/lambdák visszafordítása + + + Aszinkron metódusok (async/await) visszafordítása + + + Automatikus események visszafordítása + + + Automatikus tulajdonságok visszafordítása + + + Enumerátorok (yield return) visszafordítása + + + Kifejezés fák visszafordítása + + + foreach utasítások visszafordítása + + + lock utasítások visszafordítása + + + Lekérdező kifejezések visszafordítása + + + Karakterláncon végzett switch visszafordítása + + + Using utasítások visszafordítása + + + Névtér hozzáadása minden típushoz + + + Névtér hozzáadása az azonos nevű típusokhoz + + + Hexadecimális számok + + + Növelő és csökkentő operátorok használata + + + Hozzárendelő kifejezések használata, mint pl. while ((count = Do()) != 0) ; + + + Maximum megjelenítendő tömb elemek száma + + + Maximális karakterlánc hossz + + + A 'private' módosító hozzáadása a típus tagokhoz + + + Objektum és gyűjtemény inicializálók visszafordítása + + + Egy egyedi attribútum mutatása soronként + + + Üres alapértelmezett konstrukturok eltávolítása + + + A 'new delegate-class(xxx)' kicserélése 'xxx'-re + + + Mutassa a rejtett, fordító által generált típusokat és metódusokat + + + IL opcode megjegyzések mutatása + + + IL utasítás bájtok mutatása + + + Mutassa a sorszámokat és a fájlneveket, ha elérhetőek + + + Mutassa a token-eket, RVA-kat és a fájl eltolásokat + + + Mutassa az XML dokumentációt a visszafordított kódban + + + Egyéni attribútumok rendezése + + + Rendezze a metódusokat, mezőket, tulajdonságokat, eseményeket és típusokat + + + A 'System' direktívákat vegye előre a using-ok rendezésekor + + + 'internal' módosító hozzáadása a típusokhoz + + + Használja a hibakeresési szimbólumok szerinti változó neveket, ha elérhetőek + + + Tagok rendezése a forráskód szerint + + diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.it.resx b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.it.resx new file mode 100644 index 0000000..8320f3f --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.it.resx @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Aggiungere mediante dichiarazioni + + + Consentire gli inizializzatori di campo + + + Generare sempre le variabili di eccezione in blocchi catch a meno che tipo non sia un oggetto + + + Ordine di decompilazione + + + Decompila metodi/lambda anonimi + + + Decompila metodi async (async/await) + + + Decompila gli eventi automatici + + + Decompila proprietà automatiche + + + Decompila gli enumeratori (yield return) + + + Decompila alberi espressione + + + Decompila istruzioni foreach + + + Decompila istruzioni lock + + + Decompila espressioni query + + + Decompila switch sulla stringa + + + Decompila usando istruzioni + + + Aggiungi spazi dei nomi per tutti i tipi + + + Aggiungi uno spazio dei nomi per i tipi con lo stesso nome + + + Numeri esadecimali + + + Utilizza operatori di incremento e decremento + + + Utilizza le espressioni di assegnazione così come nel while ((count = Do()) != 0) ; + + + Numero massimo di elementi della matrice da mostrare + + + Lunghezza massima della stringa + + + Aggiungi il modificatore 'private' ai membri di tipo + + + Decompila oggetto o inizializzatori di collezione + + + Mostra un attributo personalizzato per riga + + + Rimuovi i costruttori predefiniti vuoti + + + Sostituire 'nuova delegate-class(xxx)' con 'xxx' + + + Visualizza i metodi e i tipi nascosti generati dal compilatore + + + Visualizza il commento sul codice operativo IL + + + Visualizza i byte di instruzione del codie IL + + + Mostra i numeri di linea ed i nomi dei file, se disponibili + + + Visualizza token, RVA e offset del file + + + Visualizza documentazione XML nel codice decompilato + + + Ordina attributi personalizzati + + + Ordina metodi, campi, proprietà, eventi e tipi + + + Porta le direttive 'System' al primo posto quando si usa l'ordinamento + + + Aggiungi il modificatore 'internal' ai tipi + + + Utilizza nomi di variabili da simboli di debug, se disponibili + + + Ordina i membri nell'ordine del codice sorgente + + \ No newline at end of file diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.pt-BR.resx b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.pt-BR.resx new file mode 100644 index 0000000..4389abe --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.pt-BR.resx @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Adicionar declarações 'using' + + + Permitir inicializadores de campo + + + Sempre gerar variáveis de exceção em blocos catch, a menos que o tipo seja Object + + + Ordem de descompilação + + + Descompilar métodos/lambdas anônimos + + + Descompilar métodos assíncronos (async/await) + + + Descompilar eventos automáticos + + + Descompilar propriedades automáticas + + + Descompilar enumeradores (yield return) + + + Descompilar árvores de expressão + + + Descompilar instruções foreach + + + Descompilar instruções lock + + + Descompilar expressões query + + + Descompilar switch on string + + + Descompilar usando instruções + + + Adicionar namespaces a todos os tipos + + + Adicionar um namespace para tipos com o mesmo nome + + + Números hexadecimais + + + Usar operadores de incremento e decremento + + + Utilize expressões de atribuição, como em while ((count = Do()) != 0) ; + + + Número máximo de elementos do array para mostrar + + + Comprimento máximo do texto + + + Adicionar modificador 'private' aos membros de tipo + + + Descompilar objetos ou inicializadores de coleção + + + Mostrar um atributo personalizado por linha + + + Remover os construtores padrão vazio + + + Substituir 'new delegate-class(xxx)' por 'xxx' + + + Mostrar tipos e métodos escondidos gerados pelo compilador + + + Mostrar comentários IL opcode + + + Mostrar bytes de instruções IL + + + Mostrar números de linhas e nomes de arquivos, se disponível + + + Mostrar tokens, RVAs e arquivos offsets + + + Exibir a documentação XML no código descompilado + + + Classificar atributos personalizados + + + Classificar métodos, campos, propriedades, eventos e tipos + + + Posicionar diretivas do 'Sistema' primeiro ao classificar + + + Adicionar modificador 'interno' aos tipos + + + Utilizar nomes de variáveis de símbolos de depuração, se disponíveis + + + Classificar membros em ordem de código fonte + + \ No newline at end of file diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.pt-PT.resx b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.pt-PT.resx new file mode 100644 index 0000000..d434f3e --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.pt-PT.resx @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Adicionar usando declarações + + + Permitir inicializadores de campo + + + Sempre gerar variáveis de exceção em blocos catch, a menos que o tipo seja Object + + + Ordem de descompilação + + + Descompilar métodos/lambdas anônimos + + + Descompilar métodos assíncronos (async/await) + + + Descompilar eventos automáticos + + + Descompilar propriedades automáticas + + + Descompilar enumerators (yield return) + + + Descompilar árvores de expressão + + + Descompilar declarações foreach + + + Descompilar instruções lock + + + Descompilar expressões de consulta + + + Descompilar switch on string + + + Descompilar instruções using + + + Adicionar namespaces para todos os tipos + + + Adicionar um namespace para tipos com o mesmo nome + + + Números hexadecimais + + + Usar operadores de incremento e decremento + + + Usar expressões de atribuição, tal como em while ((cont = Do())! = 0); + + + Número máximo de elementos em array para mostrar + + + Comprimento máximo do texto + + + Adicionar o modificador 'private' aos membros de tipo + + + Descompilar objetos ou inicializadores de coleção + + + Mostrar um atributo personalizado por linha + + + Remover construtores padrão vazios + + + Substituir 'new delegate-class(xxx)' por 'xxx' + + + Mostrar tipos e métodos escondidos gerados pelo compilador + + + Mostrar comentários IL opcode + + + Mostrar bytes de instruções IL + + + Mostrar números de linha e nomes de ficheiro se disponíveis + + + Mostrar tokens, RVAs e offsets de ficheiros + + + Mostrar a documentação XML no código descompilado + + + Ordenar atributos personalizados + + + Ordenar métodos, campos, propriedades, eventos e tipos + + + Posicionar diretivas 'Sistema' primeiro ao ordenar usings + + + Adicionar o modificador 'internal' aos tipos + + + Usar nomes de variáveis de símbolos de depuração, se disponível + + + Ordenar membros em ordem de código fonte + + \ No newline at end of file diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.resx b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.resx new file mode 100644 index 0000000..b8cef62 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.resx @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add using declarations + + + Allow field initializers + + + Always generate exception variables in catch blocks unless type is Object + + + Decompilation order + + + Decompile anonymous methods/lambdas + + + Decompile async methods (async/await) + + + Decompile automatic events + + + Decompile automatic properties + + + Decompile enumerators (yield return) + + + Decompile expression trees + + + Decompile foreach statements + + + Decompile lock statements + + + Decompile query expressions + + + Decompile switch on string + + + Decompile using statements + + + Add namespaces to all types + + + Add a namespace to types with the same name + + + Hexadecimal numbers + + + Use increment and decrement operators + + + Use assignment expressions such as in while ((count = Do()) != 0) ; + + + Max number of array elements to show + + + Max string length + + + Add 'private' modifier to type members + + + Decompile object or collection initializers + + + Show one custom attribute per line + + + Remove empty default constructors + + + Replace 'new delegate-class(xxx)' with 'xxx' + + + Show hidden compiler generated types and methods + + + Show IL opcode comments + + + Show IL instruction bytes + + + Show line numbers and filenames if available + + + Show tokens, RVAs and file offsets + + + Show XML documentation in decompiled code + + + Sort custom attributes + + + Sort methods, fields, properties, events and types + + + Place 'System' directives first when sorting usings + + + Add 'internal' modifier to types + + + Use variable names from debug symbols, if available + + + Order members in source code order + + \ No newline at end of file diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.ru.resx b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.ru.resx new file mode 100644 index 0000000..31a5dfb --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.ru.resx @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Добавить определения using + + + Разрешить инициализаторы полей + + + Всегда создавать переменные исключения в блоках catch, если тип не Object + + + Порядок декомпиляции + + + Декомпилировать анонимные методы/лямбды + + + Декомпилировать асинхронные методы (async/await) + + + Декомпилировать автоматические события + + + Декомпилировать автоматические свойства + + + Декомпилировать перечислители (yield return) + + + Декомпилировать деревья выражений + + + Декомпилировать операторы foreach + + + Декомпилировать операторы lock + + + Декомпилировать выражения запросов + + + Декомпилировать switch по строкам + + + Декомпилировать операторы using + + + Добавить пространства имен во все типы + + + Добавить пространство имен в типы с таким же именем + + + Шестнадцатеричные цифры + + + Использовать операторы инкремента и декремента + + + Использовать выражения присваивания, такие как в while ((count = Do()) != 0); + + + Максимальное количество отображаемых элементов массива + + + Максимальная длина строки + + + Добавить модификатор 'private' к членам типа + + + Декомпилировать инициализаторы объекта или коллекции + + + Показывать по одному атрибуту на строке + + + Удалить пустые конструкторы по умолчанию + + + Заменить «new delegate-class(xxx)» на «xxx» + + + Показывать скрытые типы и методы, созданные компилятором + + + Показать комментарии к IL-инструкциям + + + Показывать байты IL-инструкций + + + Показать номера строк и имена файлов, если имеются + + + Показывать токены, RVA и смещения в файле + + + Показать документацию XML в декомпилированном коде + + + Сортировать пользовательские атрибуты + + + Сортировать методы, поля, свойства, события и типы + + + Поместить директивы 'System' в начало при сортировке + + + Добавить модификатор 'internal' к типам + + + Использовать имена переменных из отладочных символов, при наличии + + + Сортировать члены как в исходном коде + + diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.tr.resx b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.tr.resx new file mode 100644 index 0000000..e8ea3cb --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.tr.resx @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Kullanılan bildirimleri ekle + + + Alan başlatıcılara izin ver + + + Tip Object değilken her zaman catch bloklarında her zaman isnisna değişkenler üret + + + Geri derleme sırası + + + Anonim method/lambda ları geri derle + + + Asenkron methodları (async/await) geri derle + + + Otomatik olayları geri derle + + + Otomatik özellikleri geri derle + + + Enumaretörleri (verim getirisi) geri derle + + + İfade ağaçlarını geri derle + + + Her ifade için geri derle + + + Kitli ifadeleri geri derle + + + Sorgu ifadelerini geri derle + + + Dizideki anahtarları geri derle + + + İfadeleri kullanarak geri derle + + + Tüm tipler için isim boşlukları ekle + + + Aynı isimdeki tipler için bir isim boşluğu ekle + + + Onaltılık sayılar + + + Artış ve azalış uygulayıcıları kullan + + + ((count = Do()) != 0); gibi atanmış ifadeleri kullan + + + Gösterilebilecek maksimum dizi elementi sayısı + + + Maksimum metin uzunluğu + + + Tip üyelerine 'gizli' değişimi ekle + + + Başlatıcı derlemesini veya objeyi geri derle + + + Her hat için bir nitelik göster + + + Varsayılan boş kurucuları kaldır + + + 'new delegate-class(xxx)' ı 'xxx' ile değiştir + + + Gizli derleyici tarafından oluşturulan türleri ve yöntemleri göster + + + IL opcode yorumlarını göster + + + IL komut bitlerini göster + + + Mümkünse hat numaralarını ve dosya isimlerini göster + + + Belirteçleri, RVA ları ve dosya denkleştirmelerini göster + + + Geri derlenmiş koddaki XML dökümanlarını göster + + + Özel nitelikleri sınıflandır + + + Metodları, alanları, özellikleri, olayları ve tipleri sınıflandır + + + Kullanılanları sınıflandırırken 'sistem' direktiflerini ilk olarak yerleştir + + + Tiplere 'iç' değiştirici ekle + + + Mümkünse, hata ayıklanmış sembollerden değişken isimler kullan + + + Kaynak kod sırasındaki diğer üyeler + + \ No newline at end of file diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.uk.resx b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.uk.resx new file mode 100644 index 0000000..584ccb5 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.uk.resx @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Додати за допомогою декларацій + + + Дозволити ініціалізатори поля + + + Завжди генерувати exception змінні в catch-блоках, якщо ти не Object + + + Порядок Декомпіляції + + + Декомпілювати анонімні методи/lambdas + + + Декомпілювати async методи (async/await) + + + Декомпілювати автоматичні події + + + Декомпілювати автоматичні властивості + + + Декомпілювати перечислення (yield return) + + + Декомпілювати дерева виразів + + + Декомпілювати оператори foreach + + + Декомпілювати оператори lock + + + Декомпілювати вирази запитів + + + Декомпілювати switch по стрічках + + + Декомпілювати оператори using + + + Додати простори імен для всіх типів + + + Додати простір імен в типи с таким же ім'ям + + + Шістнадцяткові числа + + + Використати оператори інкременту та декременту + + + Використати вирази присвоєння такі, як у while ((count = Do()) != 0); + + + Показувати максимальну кількість елементів масиву + + + Максимальна довжина стрічки + + + Додати модифікатор 'private' до членів типу + + + Декомпілювати ініціалізатори об'єкта чи колекції + + + Показувати по одному атрибуту на рядку + + + Видаляти пусті конструктори по замовчуванню + + + Замінити «new delegate-class(xxx)» на «xxx» + + + Показати приховані типи і методи, створені компілятором + + + Показати коментарі до IL інструкцій + + + Показати байти інструкцій IL + + + Показати номери рядків та імена файлів + + + Показувати токени, RVA і офсети у файлі + + + Показати XML документацію в декомпільованому коді + + + Сортувати користувацькі атрибути + + + Сортувати методи, поля, властивості, події і типи + + + Помістити 'System'-ні директиви на початок при сортуванні + + + Додати модифікатор 'internal' до типів + + + Використовувати імена змінних із дебаг символів, при наявності + + + Сортувати члени як в джерельному коді + + diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.zh-CN.resx b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.zh-CN.resx new file mode 100644 index 0000000..d96c4e3 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Properties/dnSpy.Decompiler.ILSpy.Core.Resources.zh-CN.resx @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 添加using声明 + + + 允许字段初始值设定项语法 + + + 在catch块中总是生成异常变量,除非类型为Object + + + 反编译顺序 + + + 反编译匿名方法/lambda方法 + + + 反编译异步方法(async/await) + + + 反编译自动生成的事件 + + + 反编译自动生成的属性 + + + 反编译枚举器(yield return) + + + 反编译表达式树(Expression Trees) + + + 反编译foreach语句 + + + 反编译lock语句 + + + 反编译查询表达式 + + + 反编译string类型的switch语句 + + + 反编译using语句 + + + 将命名空间名添加到所有类型 + + + 将命名空间名添加到具有相同名称的类型 + + + 十六进制数 + + + 使用增量和减量运算符 + + + 在流程控制语句中使用赋值表达式(如while ((count = Do()) != 0) ;) + + + 要显示的数组元素最大数量 + + + 最大字符串长度 + + + 添加private标识符到类成员 + + + 反编译对象或集合的初始值设定项 + + + 每行显示一个自定义特性 + + + 移除空的默认构造函数 + + + 将 “new delegate-class(xxx)” 替换为 “xxx” + + + 显示编译器生成的隐藏类型和方法 + + + 显示IL操作码注释 + + + 显示IL指令字节 + + + 显示行号和文件名(如果可用) + + + 显示标记,RVA和文件偏移 + + + 在反编译的代码中显示 XML 文档 + + + 排序自定义特性 + + + 排序方法、 字段、 属性、 事件和类型 + + + 在排序using时将System系列置于首位 + + + 添加internal标识符到类 + + + 如果可用,使用debug symbols声明的变量名称 + + + 按源代码顺序排序成员 + + \ No newline at end of file diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Settings/CSharpVBDecompilerSettings.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Settings/CSharpVBDecompilerSettings.cs new file mode 100644 index 0000000..b474ced --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Settings/CSharpVBDecompilerSettings.cs @@ -0,0 +1,281 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using dnSpy.Contracts.Decompiler; +using dnSpy.Decompiler.ILSpy.Core.Properties; +using dnSpy.Decompiler.Settings; +using ICSharpCode.Decompiler; + +namespace dnSpy.Decompiler.ILSpy.Core.Settings { + sealed class CSharpVBDecompilerSettings : DecompilerSettingsBase { + public DecompilerSettings Settings => decompilerSettings; + readonly DecompilerSettings decompilerSettings; + + public override int Version => decompilerSettings.SettingsVersion; + public override event EventHandler? VersionChanged; + + public CSharpVBDecompilerSettings(DecompilerSettings? decompilerSettings = null) { + this.decompilerSettings = decompilerSettings ?? new DecompilerSettings(); + options = CreateOptions().ToArray(); + this.decompilerSettings.SettingsVersionChanged += DecompilerSettings_SettingsVersionChanged; + } + + void DecompilerSettings_SettingsVersionChanged(object? sender, EventArgs e) => VersionChanged?.Invoke(this, EventArgs.Empty); + + public override DecompilerSettingsBase Clone() => new CSharpVBDecompilerSettings(decompilerSettings.Clone()); + + public override IEnumerable Options => options; + readonly IDecompilerOption[] options; + + IEnumerable CreateOptions() { + yield return new DecompilerOption(DecompilerOptionConstants.MemberOrder_GUID, + () => GetMemberOrder(), a => SetMemberOrder(a)) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_DecompilationOrder, + Name = DecompilerOptionConstants.MemberOrder_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.AnonymousMethods_GUID, + () => decompilerSettings.AnonymousMethods, a => decompilerSettings.AnonymousMethods = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_DecompileAnonMethods, + Name = DecompilerOptionConstants.AnonymousMethods_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.ExpressionTrees_GUID, + () => decompilerSettings.ExpressionTrees, a => decompilerSettings.ExpressionTrees = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_DecompileExprTrees, + Name = DecompilerOptionConstants.ExpressionTrees_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.YieldReturn_GUID, + () => decompilerSettings.YieldReturn, a => decompilerSettings.YieldReturn = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_DecompileEnumerators, + Name = DecompilerOptionConstants.YieldReturn_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.AsyncAwait_GUID, + () => decompilerSettings.AsyncAwait, a => decompilerSettings.AsyncAwait = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_DecompileAsyncMethods, + Name = DecompilerOptionConstants.AsyncAwait_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.AutomaticProperties_GUID, + () => decompilerSettings.AutomaticProperties, a => decompilerSettings.AutomaticProperties = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_DecompileAutoProps, + Name = DecompilerOptionConstants.AutomaticProperties_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.AutomaticEvents_GUID, + () => decompilerSettings.AutomaticEvents, a => decompilerSettings.AutomaticEvents = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_DecompileAutoEvents, + Name = DecompilerOptionConstants.AutomaticEvents_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.UsingStatement_GUID, + () => decompilerSettings.UsingStatement, a => decompilerSettings.UsingStatement = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_DecompileUsingStatements, + Name = DecompilerOptionConstants.UsingStatement_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.ForEachStatement_GUID, + () => decompilerSettings.ForEachStatement, a => decompilerSettings.ForEachStatement = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_DecompileForeachStatements, + Name = DecompilerOptionConstants.ForEachStatement_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.LockStatement_GUID, + () => decompilerSettings.LockStatement, a => decompilerSettings.LockStatement = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_DecompileLockStatements, + Name = DecompilerOptionConstants.LockStatement_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.SwitchStatementOnString_GUID, + () => decompilerSettings.SwitchStatementOnString, a => decompilerSettings.SwitchStatementOnString = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_DecompileSwitchOnString, + Name = DecompilerOptionConstants.SwitchStatementOnString_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.UsingDeclarations_GUID, + () => decompilerSettings.UsingDeclarations, a => decompilerSettings.UsingDeclarations = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_AddUsingDeclarations, + Name = DecompilerOptionConstants.UsingDeclarations_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.QueryExpressions_GUID, + () => decompilerSettings.QueryExpressions, a => decompilerSettings.QueryExpressions = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_DecompileQueryExpr, + Name = DecompilerOptionConstants.QueryExpressions_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.FullyQualifyAmbiguousTypeNames_GUID, + () => decompilerSettings.FullyQualifyAmbiguousTypeNames, a => decompilerSettings.FullyQualifyAmbiguousTypeNames = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_FullyQualifyAmbiguousTypeNames, + Name = DecompilerOptionConstants.FullyQualifyAmbiguousTypeNames_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.FullyQualifyAllTypes_GUID, + () => decompilerSettings.FullyQualifyAllTypes, a => decompilerSettings.FullyQualifyAllTypes = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_FullyQualifyAllTypes, + Name = DecompilerOptionConstants.FullyQualifyAllTypes_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.UseDebugSymbols_GUID, + () => decompilerSettings.UseDebugSymbols, a => decompilerSettings.UseDebugSymbols = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_UseLocalNameFromSyms, + Name = DecompilerOptionConstants.UseDebugSymbols_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.ObjectOrCollectionInitializers_GUID, + () => decompilerSettings.ObjectOrCollectionInitializers, a => decompilerSettings.ObjectOrCollectionInitializers = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_ObjectOrCollectionInitializers, + Name = DecompilerOptionConstants.ObjectOrCollectionInitializers_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.ShowXmlDocumentation_GUID, + () => decompilerSettings.ShowXmlDocumentation, a => decompilerSettings.ShowXmlDocumentation = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_ShowXMLDocComments, + Name = DecompilerOptionConstants.ShowXmlDocumentation_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.RemoveEmptyDefaultConstructors_GUID, + () => decompilerSettings.RemoveEmptyDefaultConstructors, a => decompilerSettings.RemoveEmptyDefaultConstructors = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_RemoveEmptyDefaultCtors, + Name = DecompilerOptionConstants.RemoveEmptyDefaultConstructors_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.IntroduceIncrementAndDecrement_GUID, + () => decompilerSettings.IntroduceIncrementAndDecrement, a => decompilerSettings.IntroduceIncrementAndDecrement = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_IntroduceIncrementAndDecrement, + Name = DecompilerOptionConstants.IntroduceIncrementAndDecrement_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.MakeAssignmentExpressions_GUID, + () => decompilerSettings.MakeAssignmentExpressions, a => decompilerSettings.MakeAssignmentExpressions = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_MakeAssignmentExpressions, + Name = DecompilerOptionConstants.MakeAssignmentExpressions_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.AlwaysGenerateExceptionVariableForCatchBlocksUnlessTypeIsObject_GUID, + () => decompilerSettings.AlwaysGenerateExceptionVariableForCatchBlocksUnlessTypeIsObject, a => decompilerSettings.AlwaysGenerateExceptionVariableForCatchBlocksUnlessTypeIsObject = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_AlwaysGenerateExceptionVariableForCatchBlocksUnlessTypeIsObject, + Name = DecompilerOptionConstants.AlwaysGenerateExceptionVariableForCatchBlocksUnlessTypeIsObject_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.ShowTokenAndRvaComments_GUID, + () => decompilerSettings.ShowTokenAndRvaComments, a => decompilerSettings.ShowTokenAndRvaComments = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_ShowTokensRvasOffsets, + Name = DecompilerOptionConstants.ShowTokenAndRvaComments_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.SortMembers_GUID, + () => decompilerSettings.SortMembers, a => decompilerSettings.SortMembers = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_SortMethods, + Name = DecompilerOptionConstants.SortMembers_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.ForceShowAllMembers_GUID, + () => decompilerSettings.ForceShowAllMembers, a => decompilerSettings.ForceShowAllMembers = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_ShowCompilerGeneratedTypes, + Name = DecompilerOptionConstants.ForceShowAllMembers_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.SortSystemUsingStatementsFirst_GUID, + () => decompilerSettings.SortSystemUsingStatementsFirst, a => decompilerSettings.SortSystemUsingStatementsFirst = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_SortSystemFirst, + Name = DecompilerOptionConstants.SortSystemUsingStatementsFirst_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.MaxArrayElements_GUID, + () => decompilerSettings.MaxArrayElements, a => decompilerSettings.MaxArrayElements = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_MaxArrayElements, + Name = DecompilerOptionConstants.MaxArrayElements_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.MaxStringLength_GUID, + () => decompilerSettings.MaxStringLength, a => decompilerSettings.MaxStringLength = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_MaxStringLength, + Name = DecompilerOptionConstants.MaxStringLength_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.SortCustomAttributes_GUID, + () => decompilerSettings.SortCustomAttributes, a => decompilerSettings.SortCustomAttributes = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_SortCustomAttributes, + Name = DecompilerOptionConstants.SortCustomAttributes_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.UseSourceCodeOrder_GUID, + () => decompilerSettings.UseSourceCodeOrder, a => decompilerSettings.UseSourceCodeOrder = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_UseSourceCodeOrder, + Name = DecompilerOptionConstants.UseSourceCodeOrder_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.AllowFieldInitializers_GUID, + () => decompilerSettings.AllowFieldInitializers, a => decompilerSettings.AllowFieldInitializers = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_AllowFieldInitializers, + Name = DecompilerOptionConstants.AllowFieldInitializers_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.OneCustomAttributePerLine_GUID, + () => decompilerSettings.OneCustomAttributePerLine, a => decompilerSettings.OneCustomAttributePerLine = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_OneCustomAttributePerLine, + Name = DecompilerOptionConstants.OneCustomAttributePerLine_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.TypeAddInternalModifier_GUID, + () => decompilerSettings.TypeAddInternalModifier, a => decompilerSettings.TypeAddInternalModifier = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_TypeAddInternalModifier, + Name = DecompilerOptionConstants.TypeAddInternalModifier_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.MemberAddPrivateModifier_GUID, + () => decompilerSettings.MemberAddPrivateModifier, a => decompilerSettings.MemberAddPrivateModifier = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_MemberAddPrivateModifier, + Name = DecompilerOptionConstants.MemberAddPrivateModifier_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.RemoveNewDelegateClass_GUID, + () => decompilerSettings.RemoveNewDelegateClass, a => decompilerSettings.RemoveNewDelegateClass = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_RemoveNewDelegateClass, + Name = DecompilerOptionConstants.RemoveNewDelegateClass_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.HexadecimalNumbers_GUID, + () => decompilerSettings.HexadecimalNumbers, a => decompilerSettings.HexadecimalNumbers = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_HexadecimalNumbers, + Name = DecompilerOptionConstants.HexadecimalNumbers_NAME, + }; + } + + string GetMemberOrder() => + GetMemberOrderString(decompilerSettings.DecompilationObject0) + + GetMemberOrderString(decompilerSettings.DecompilationObject1) + + GetMemberOrderString(decompilerSettings.DecompilationObject2) + + GetMemberOrderString(decompilerSettings.DecompilationObject3) + + GetMemberOrderString(decompilerSettings.DecompilationObject4); + + static string GetMemberOrderString(DecompilationObject d) { + switch (d) { + case DecompilationObject.NestedTypes: return "t"; + case DecompilationObject.Fields: return "f"; + case DecompilationObject.Events: return "e"; + case DecompilationObject.Properties: return "p"; + case DecompilationObject.Methods: return "m"; + default: + Debug.Fail("Shouldn't be here"); + return "?"; + } + } + + void SetMemberOrder(string s) { + if (s is null || s.Length != 5) + return; + decompilerSettings.DecompilationObject0 = GetDecompilationObject(s[0]) ?? decompilerSettings.DecompilationObject0; + decompilerSettings.DecompilationObject1 = GetDecompilationObject(s[1]) ?? decompilerSettings.DecompilationObject1; + decompilerSettings.DecompilationObject2 = GetDecompilationObject(s[2]) ?? decompilerSettings.DecompilationObject2; + decompilerSettings.DecompilationObject3 = GetDecompilationObject(s[3]) ?? decompilerSettings.DecompilationObject3; + decompilerSettings.DecompilationObject4 = GetDecompilationObject(s[4]) ?? decompilerSettings.DecompilationObject4; + } + + static DecompilationObject? GetDecompilationObject(char c) { + switch (c) { + case 't': return DecompilationObject.NestedTypes; + case 'f': return DecompilationObject.Fields; + case 'e': return DecompilationObject.Events; + case 'p': return DecompilationObject.Properties; + case 'm': return DecompilationObject.Methods; + } + return null; + } + + public override bool Equals(object? obj) { + var other = obj as CSharpVBDecompilerSettings; + return other is not null && decompilerSettings.Equals(other.decompilerSettings); + } + + public override int GetHashCode() => decompilerSettings.GetHashCode(); + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Settings/DecompilerSettingsService.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Settings/DecompilerSettingsService.cs new file mode 100644 index 0000000..c3bf436 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Settings/DecompilerSettingsService.cs @@ -0,0 +1,48 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System.Threading; + +namespace dnSpy.Decompiler.ILSpy.Core.Settings { + class DecompilerSettingsService { + /// + /// Should only be used indirectly by dnSpy.Console.exe + /// + public static DecompilerSettingsService __Instance_DONT_USE { + get { + if (__instance_DONT_USE is null) + Interlocked.CompareExchange(ref __instance_DONT_USE, new DecompilerSettingsService(), null); + return __instance_DONT_USE!; + } + } + static DecompilerSettingsService? __instance_DONT_USE; + + protected DecompilerSettingsService() { + CSharpVBDecompilerSettings = new CSharpVBDecompilerSettings(); + ILDecompilerSettings = new ILDecompilerSettings(); + } + + public CSharpVBDecompilerSettings CSharpVBDecompilerSettings { get; protected set; } + public ILDecompilerSettings ILDecompilerSettings { get; protected set; } + +#if DEBUG + public ILAstDecompilerSettings ILAstDecompilerSettings { get; } = new ILAstDecompilerSettings(); +#endif + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Settings/ILAstDecompilerSettings.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Settings/ILAstDecompilerSettings.cs new file mode 100644 index 0000000..1469df3 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Settings/ILAstDecompilerSettings.cs @@ -0,0 +1,46 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +#if DEBUG +using System; +using System.Collections.Generic; +using dnSpy.Contracts.Decompiler; + +namespace dnSpy.Decompiler.ILSpy.Core.Settings { + sealed class ILAstDecompilerSettings : DecompilerSettingsBase { + public override int Version => 0; + public override event EventHandler? VersionChanged { add { } remove { } } + + public ILAstDecompilerSettings() { + } + + ILAstDecompilerSettings(ILAstDecompilerSettings other) { + } + + public override DecompilerSettingsBase Clone() => new ILAstDecompilerSettings(this); + + public override IEnumerable Options { + get { yield break; } + } + + public override bool Equals(object? obj) => obj is ILAstDecompilerSettings; + public override int GetHashCode() => 0; + } +} +#endif diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Settings/ILDecompilerSettings.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Settings/ILDecompilerSettings.cs new file mode 100644 index 0000000..585e229 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Settings/ILDecompilerSettings.cs @@ -0,0 +1,95 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; +using System.Collections.Generic; +using System.Linq; +using dnSpy.Contracts.Decompiler; +using dnSpy.Decompiler.ILSpy.Core.Properties; +using dnSpy.Decompiler.Settings; + +namespace dnSpy.Decompiler.ILSpy.Core.Settings { + sealed class ILDecompilerSettings : DecompilerSettingsBase { + public ILSettings Settings => ilSettings; + readonly ILSettings ilSettings; + + public override int Version => ilSettings.SettingsVersion; + public override event EventHandler? VersionChanged; + + public ILDecompilerSettings(ILSettings? ilSettings = null) { + this.ilSettings = ilSettings ?? new ILSettings(); + options = CreateOptions().ToArray(); + this.ilSettings.SettingsVersionChanged += ILSettings_SettingsVersionChanged; + } + + void ILSettings_SettingsVersionChanged(object? sender, EventArgs e) => VersionChanged?.Invoke(this, EventArgs.Empty); + + public override DecompilerSettingsBase Clone() => new ILDecompilerSettings(ilSettings.Clone()); + + public override IEnumerable Options => options; + readonly IDecompilerOption[] options; + + IEnumerable CreateOptions() { + yield return new DecompilerOption(DecompilerOptionConstants.ShowILComments_GUID, + () => ilSettings.ShowILComments, a => ilSettings.ShowILComments = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_ShowILComments, + Name = DecompilerOptionConstants.ShowILComments_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.ShowXmlDocumentation_GUID, + () => ilSettings.ShowXmlDocumentation, a => ilSettings.ShowXmlDocumentation = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_ShowXMLDocComments, + Name = DecompilerOptionConstants.ShowXmlDocumentation_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.ShowTokenAndRvaComments_GUID, + () => ilSettings.ShowTokenAndRvaComments, a => ilSettings.ShowTokenAndRvaComments = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_ShowTokensRvasOffsets, + Name = DecompilerOptionConstants.ShowTokenAndRvaComments_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.ShowILBytes_GUID, + () => ilSettings.ShowILBytes, a => ilSettings.ShowILBytes = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_ShowILInstrBytes, + Name = DecompilerOptionConstants.ShowILBytes_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.SortMembers_GUID, + () => ilSettings.SortMembers, a => ilSettings.SortMembers = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_SortMethods, + Name = DecompilerOptionConstants.SortMembers_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.ShowPdbInfo_GUID, + () => ilSettings.ShowPdbInfo, a => ilSettings.ShowPdbInfo = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_ShowPdbInfo, + Name = DecompilerOptionConstants.ShowPdbInfo_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.MaxStringLength_GUID, + () => ilSettings.MaxStringLength, a => ilSettings.MaxStringLength = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_MaxStringLength, + Name = DecompilerOptionConstants.MaxStringLength_NAME, + }; + yield return new DecompilerOption(DecompilerOptionConstants.HexadecimalNumbers_GUID, + () => ilSettings.HexadecimalNumbers, a => ilSettings.HexadecimalNumbers = a) { + Description = dnSpy_Decompiler_ILSpy_Core_Resources.DecompilerSettings_HexadecimalNumbers, + Name = DecompilerOptionConstants.HexadecimalNumbers_NAME, + }; + } + + public override bool Equals(object? obj) => + obj is ILDecompilerSettings && ilSettings.Equals(((ILDecompilerSettings)obj).ilSettings); + public override int GetHashCode() => ilSettings.GetHashCode(); + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Settings/ILSettings.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Settings/ILSettings.cs new file mode 100644 index 0000000..1ba430d --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Settings/ILSettings.cs @@ -0,0 +1,175 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; +using System.Threading; +using dnSpy.Contracts.MVVM; + +namespace dnSpy.Decompiler.ILSpy.Core.Settings { + class ILSettings : ViewModelBase { + protected virtual void OnModified() { } + public event EventHandler? SettingsVersionChanged; + + void OptionsChanged() { + Interlocked.Increment(ref settingsVersion); + OnModified(); + SettingsVersionChanged?.Invoke(this, EventArgs.Empty); + } + + public int SettingsVersion => settingsVersion; + volatile int settingsVersion; + + public bool ShowILComments { + get => showILComments; + set { + if (showILComments != value) { + showILComments = value; + OnPropertyChanged(nameof(ShowILComments)); + OptionsChanged(); + } + } + } + bool showILComments = false; + + public bool ShowXmlDocumentation { + get => showXmlDocumentation; + set { + if (showXmlDocumentation != value) { + showXmlDocumentation = value; + OnPropertyChanged(nameof(ShowXmlDocumentation)); + OptionsChanged(); + } + } + } + bool showXmlDocumentation = true; + + public bool ShowTokenAndRvaComments { + get => showTokenAndRvaComments; + set { + if (showTokenAndRvaComments != value) { + showTokenAndRvaComments = value; + OnPropertyChanged(nameof(ShowTokenAndRvaComments)); + OptionsChanged(); + } + } + } + bool showTokenAndRvaComments = true; + + public bool ShowILBytes { + get => showILBytes; + set { + if (showILBytes != value) { + showILBytes = value; + OnPropertyChanged(nameof(ShowILBytes)); + OptionsChanged(); + } + } + } + bool showILBytes = true; + + public bool SortMembers { + get => sortMembers; + set { + if (sortMembers != value) { + sortMembers = value; + OnPropertyChanged(nameof(SortMembers)); + OptionsChanged(); + } + } + } + bool sortMembers = false; + + public bool ShowPdbInfo { + get => showPdbInfo; + set { + if (showPdbInfo != value) { + showPdbInfo = value; + OnPropertyChanged(nameof(ShowPdbInfo)); + OptionsChanged(); + } + } + } + bool showPdbInfo = true; + + public int MaxStringLength { + get => maxStringLength; + set { + if (maxStringLength != value) { + maxStringLength = value; + OnPropertyChanged(nameof(MaxStringLength)); + OptionsChanged(); + } + } + } + int maxStringLength = ICSharpCode.Decompiler.DecompilerSettings.ConstMaxStringLength; + + public bool HexadecimalNumbers { + get { return hexadecimalNumbers; } + set { + if (hexadecimalNumbers != value) { + hexadecimalNumbers = value; + OnPropertyChanged(nameof(HexadecimalNumbers)); + } + } + } + bool hexadecimalNumbers = false; + + public ILSettings Clone() => CopyTo(new ILSettings()); + + public ILSettings CopyTo(ILSettings other) { + other.ShowILComments = ShowILComments; + other.ShowXmlDocumentation = ShowXmlDocumentation; + other.ShowTokenAndRvaComments = ShowTokenAndRvaComments; + other.ShowILBytes = ShowILBytes; + other.SortMembers = SortMembers; + other.ShowPdbInfo = ShowPdbInfo; + other.MaxStringLength = MaxStringLength; + other.HexadecimalNumbers = HexadecimalNumbers; + return other; + } + + public override bool Equals(object? obj) { + var other = obj as ILSettings; + return other is not null && + ShowILComments == other.ShowILComments && + ShowXmlDocumentation == other.ShowXmlDocumentation && + ShowTokenAndRvaComments == other.ShowTokenAndRvaComments && + ShowILBytes == other.ShowILBytes && + SortMembers == other.SortMembers && + ShowPdbInfo == other.ShowPdbInfo && + MaxStringLength == other.MaxStringLength && + HexadecimalNumbers == other.HexadecimalNumbers; + } + + public override int GetHashCode() { + uint h = 0; + + if (ShowILComments) h ^= 0x80000000; + if (ShowXmlDocumentation) h ^= 0x40000000; + if (ShowTokenAndRvaComments) h ^= 0x20000000; + if (ShowILBytes) h ^= 0x10000000; + if (SortMembers) h ^= 0x08000000; + if (ShowPdbInfo) h ^= 0x04000000; + h ^= (uint)MaxStringLength; + if (HexadecimalNumbers) h ^= 0x02000000; + + return (int)h; + } + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Text/ContentTypesInternal.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Text/ContentTypesInternal.cs new file mode 100644 index 0000000..d4835e1 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Text/ContentTypesInternal.cs @@ -0,0 +1,28 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +namespace dnSpy.Decompiler.ILSpy.Core.Text { + static class ContentTypesInternal { + public const string DecompilerILSpy = "Decompiler ILSpy"; + public const string CSharpILSpy = "C# ILSpy"; + public const string VisualBasicILSpy = "VB ILSpy"; + public const string ILILSpy = "IL ILSpy"; + public const string ILAstILSpy = "ILAst ILSpy"; + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/VisualBasic/ILSpyEnvironmentProvider.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/VisualBasic/ILSpyEnvironmentProvider.cs new file mode 100644 index 0000000..54b037f --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/VisualBasic/ILSpyEnvironmentProvider.cs @@ -0,0 +1,172 @@ +// 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 System.Collections.Generic; +using System.Linq; +using System.Text; +using dnlib.DotNet; +using dnSpy.Contracts.Decompiler; +using ICSharpCode.Decompiler.Ast; +using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.NRefactory.VB.Visitors; + +namespace dnSpy.Decompiler.ILSpy.Core.VisualBasic { + sealed class ILSpyEnvironmentProvider : IEnvironmentProvider { + public string RootNamespace => ""; + + readonly StringBuilder sb; + + public ILSpyEnvironmentProvider(StringBuilder? sb = null) => this.sb = sb ?? new StringBuilder(); + + public string GetTypeNameForAttribute(ICSharpCode.NRefactory.CSharp.Attribute attribute) { + var mr = attribute.Type.Annotations + .OfType() + .FirstOrDefault(); + return mr is null ? string.Empty : mr.FullName; + } + + /* +var annotation = type.Annotation(); +if (annotation is null ) + return null; + +IEntity current = null; +if (entity is not null) { + var typeInfo = entity.Annotation(); + current = loader.ReadTypeReference(typeInfo).Resolve(context).GetDefinition(); +} + +return loader.ReadTypeReference(annotation, entity: current).Resolve(context);*/ + public ICSharpCode.NRefactory.TypeSystem.IType ResolveType(ICSharpCode.NRefactory.VB.Ast.AstType type, ICSharpCode.NRefactory.VB.Ast.TypeDeclaration? entity = null) => SpecialType.UnknownType; + + public TypeKind GetTypeKindForAstType(ICSharpCode.NRefactory.CSharp.AstType type) { + var annotation = type.Annotation(); + if (annotation is null) + return TypeKind.Unknown; + + var definition = annotation.ResolveTypeDef(); + if (definition is null) + return TypeKind.Unknown; + if (definition.IsClass) + return TypeKind.Class; + if (definition.IsInterface) + return TypeKind.Interface; + if (definition.IsEnum) + return TypeKind.Enum; + if (definition.IsValueType) + return TypeKind.Struct; + + return TypeKind.Unknown; + } + + public TypeCode ResolveExpression(ICSharpCode.NRefactory.CSharp.Expression expression) { + var annotation = expression.Annotations.OfType().FirstOrDefault(); + + if (annotation is null || annotation.InferredType is null) + return TypeCode.Object; + + var definition = annotation.InferredType.GetScopeTypeDefOrRef().ResolveTypeDef(); + + if (definition is null) + return TypeCode.Object; + + switch (definition.FullName) { + case "System.String": + return TypeCode.String; + default: + break; + } + + return TypeCode.Object; + } + + public bool? IsReferenceType(ICSharpCode.NRefactory.CSharp.Expression expression) { + if (expression is ICSharpCode.NRefactory.CSharp.NullReferenceExpression) + return true; + + var annotation = expression.Annotations.OfType().FirstOrDefault(); + + if (annotation is null || annotation.InferredType is null) + return null; + + var definition = annotation.InferredType.GetScopeTypeDefOrRef().ResolveTypeDef(); + + if (definition is null) + return null; + + return !definition.IsValueType; + } + + public IEnumerable CreateMemberSpecifiersForInterfaces(IEnumerable interfaces) { + foreach (var type in interfaces) { + var def = type.Annotation().ResolveTypeDef(); + if (def is null) + continue; + foreach (var method in def.Methods.Where(m => !m.Name.StartsWith("get_") && !m.Name.StartsWith("set_"))) { + yield return ICSharpCode.NRefactory.VB.Ast.InterfaceMemberSpecifier.CreateWithColor((ICSharpCode.NRefactory.VB.Ast.AstType)type.Clone(), method.Name, VisualBasicMetadataTextColorProvider.Instance.GetColor(method)); + } + + foreach (var property in def.Properties) { + yield return ICSharpCode.NRefactory.VB.Ast.InterfaceMemberSpecifier.CreateWithColor((ICSharpCode.NRefactory.VB.Ast.AstType)type.Clone(), property.Name, VisualBasicMetadataTextColorProvider.Instance.GetColor(property)); + } + } + } + + public bool HasEvent(ICSharpCode.NRefactory.VB.Ast.Expression expression) => expression.Annotation() is not null; + + public bool IsMethodGroup(ICSharpCode.NRefactory.CSharp.Expression expression) { + var annotation = expression.Annotation(); + if (annotation is null) + return false; + return expression.Annotation() is null && expression.Annotation() is null; + } + + public ICSharpCode.NRefactory.CSharp.ParameterDeclaration[] GetParametersForProperty(ICSharpCode.NRefactory.CSharp.PropertyDeclaration property) { + var propInfo = property.Annotation(); + + if (propInfo is null) + return new ICSharpCode.NRefactory.CSharp.ParameterDeclaration[0]; + + sb.Clear(); + var getMethod = propInfo.GetMethod; + if (getMethod is not null) + return getMethod.Parameters.Where(p => p.IsNormalMethodParameter).Select(p => new ICSharpCode.NRefactory.CSharp.ParameterDeclaration(AstBuilder.ConvertType(p.Type, sb), p.Name, GetModifiers(p))).ToArray(); + var setMethod = propInfo.SetMethod; + if (setMethod is not null) { + var ps = setMethod.Parameters.Where(p => p.IsNormalMethodParameter).ToArray(); + if (ps.Length > 1) + return ps.Take(ps.Length - 1).Select(p => new ICSharpCode.NRefactory.CSharp.ParameterDeclaration(AstBuilder.ConvertType(p.Type, sb), p.Name, GetModifiers(p))).ToArray(); + } + + return new ICSharpCode.NRefactory.CSharp.ParameterDeclaration[0]; + } + + ICSharpCode.NRefactory.CSharp.ParameterModifier GetModifiers(Parameter p) { + var pd = p.ParamDef; + if (pd is not null) { + if (pd.IsOut && pd.IsIn) + return ICSharpCode.NRefactory.CSharp.ParameterModifier.Ref; + if (pd.IsOut) + return ICSharpCode.NRefactory.CSharp.ParameterModifier.Out; + } + + return ICSharpCode.NRefactory.CSharp.ParameterModifier.None; + } + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/VisualBasic/VBDecompiler.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/VisualBasic/VBDecompiler.cs new file mode 100644 index 0000000..dda8e7a --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/VisualBasic/VBDecompiler.cs @@ -0,0 +1,333 @@ +// 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 System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using dnlib.DotNet; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Text; +using dnSpy.Decompiler.ILSpy.Core.CSharp; +using dnSpy.Decompiler.ILSpy.Core.Settings; +using dnSpy.Decompiler.ILSpy.Core.Text; +using dnSpy.Decompiler.VisualBasic; +using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.Ast; +using ICSharpCode.Decompiler.Ast.Transforms; +using ICSharpCode.NRefactory.VB; +using ICSharpCode.NRefactory.VB.Visitors; + +namespace dnSpy.Decompiler.ILSpy.Core.VisualBasic { + sealed class DecompilerProvider : IDecompilerProvider { + readonly DecompilerSettingsService decompilerSettingsService; + + // Keep the default ctor. It's used by dnSpy.Console.exe + public DecompilerProvider() + : this(DecompilerSettingsService.__Instance_DONT_USE) { + } + + public DecompilerProvider(DecompilerSettingsService decompilerSettingsService) { + Debug2.Assert(decompilerSettingsService is not null); + this.decompilerSettingsService = decompilerSettingsService ?? throw new ArgumentNullException(nameof(decompilerSettingsService)); + } + + public IEnumerable Create() { + yield return new VBDecompiler(decompilerSettingsService.CSharpVBDecompilerSettings); + } + } + + /// + /// Decompiler logic for VB. + /// + sealed class VBDecompiler : DecompilerBase { + readonly Predicate? transformAbortCondition = null; + readonly bool showAllMembers = false; + readonly Func createBuilderCache; + + public override DecompilerSettingsBase Settings => langSettings; + readonly CSharpVBDecompilerSettings langSettings; + + public override double OrderUI => DecompilerConstants.VISUALBASIC_ILSPY_ORDERUI; + public override MetadataTextColorProvider MetadataTextColorProvider => VisualBasicMetadataTextColorProvider.Instance; + + public VBDecompiler(CSharpVBDecompilerSettings langSettings) { + this.langSettings = langSettings; + createBuilderCache = () => new BuilderCache(this.langSettings.Settings.SettingsVersion); + } + + public override string ContentTypeString => ContentTypesInternal.VisualBasicILSpy; + public override string GenericNameUI => DecompilerConstants.GENERIC_NAMEUI_VISUALBASIC; + public override string UniqueNameUI => "Visual Basic"; + public override Guid GenericGuid => DecompilerConstants.LANGUAGE_VISUALBASIC; + public override Guid UniqueGuid => DecompilerConstants.LANGUAGE_VISUALBASIC_ILSPY; + public override string FileExtension => ".vb"; + public override string? ProjectFileExtension => ".vbproj"; + + public override void WriteCommentBegin(IDecompilerOutput output, bool addSpace) { + if (addSpace) + output.Write("' ", BoxedTextColor.Comment); + else + output.Write("'", BoxedTextColor.Comment); + } + + public override void WriteCommentEnd(IDecompilerOutput output, bool addSpace) { } + + DecompilerSettings GetDecompilerSettings() { + var settings = langSettings.Settings.Clone(); + // Different default access modifiers between C#/VB, so for now ignore the options + settings.TypeAddInternalModifier = true; + settings.MemberAddPrivateModifier = true; + return settings; + } + + public override void Decompile(AssemblyDef asm, IDecompilerOutput output, DecompilationContext ctx) { + WriteAssembly(asm, output, ctx); + + using (ctx.DisableAssemblyLoad()) { + var state = CreateAstBuilder(ctx, GetDecompilerSettings(), currentModule: asm.ManifestModule); + try { + state.AstBuilder.AddAssembly(asm.ManifestModule, true, true, false); + RunTransformsAndGenerateCode(ref state, output, ctx); + } + finally { + state.Dispose(); + } + } + } + + public override void Decompile(ModuleDef mod, IDecompilerOutput output, DecompilationContext ctx) { + WriteModule(mod, output, ctx); + + using (ctx.DisableAssemblyLoad()) { + var state = CreateAstBuilder(ctx, GetDecompilerSettings(), currentModule: mod); + try { + state.AstBuilder.AddAssembly(mod, true, false, true); + RunTransformsAndGenerateCode(ref state, output, ctx); + } + finally { + state.Dispose(); + } + } + } + + public override void Decompile(MethodDef method, IDecompilerOutput output, DecompilationContext ctx) { + WriteCommentLineDeclaringType(output, method); + var state = CreateAstBuilder(ctx, GetDecompilerSettings(), currentType: method.DeclaringType, isSingleMember: true); + try { + state.AstBuilder.AddMethod(method); + RunTransformsAndGenerateCode(ref state, output, ctx); + } + finally { + state.Dispose(); + } + } + + public override void Decompile(PropertyDef property, IDecompilerOutput output, DecompilationContext ctx) { + WriteCommentLineDeclaringType(output, property); + var state = CreateAstBuilder(ctx, GetDecompilerSettings(), currentType: property.DeclaringType, isSingleMember: true); + try { + state.AstBuilder.AddProperty(property); + RunTransformsAndGenerateCode(ref state, output, ctx); + } + finally { + state.Dispose(); + } + } + + public override void Decompile(FieldDef field, IDecompilerOutput output, DecompilationContext ctx) { + WriteCommentLineDeclaringType(output, field); + var state = CreateAstBuilder(ctx, GetDecompilerSettings(), currentType: field.DeclaringType, isSingleMember: true); + try { + state.AstBuilder.AddField(field); + RunTransformsAndGenerateCode(ref state, output, ctx); + } + finally { + state.Dispose(); + } + } + + public override void Decompile(EventDef ev, IDecompilerOutput output, DecompilationContext ctx) { + WriteCommentLineDeclaringType(output, ev); + var state = CreateAstBuilder(ctx, GetDecompilerSettings(), currentType: ev.DeclaringType, isSingleMember: true); + try { + state.AstBuilder.AddEvent(ev); + RunTransformsAndGenerateCode(ref state, output, ctx); + } + finally { + state.Dispose(); + } + } + + public override void Decompile(TypeDef type, IDecompilerOutput output, DecompilationContext ctx) { + var state = CreateAstBuilder(ctx, GetDecompilerSettings(), currentType: type); + try { + state.AstBuilder.AddType(type); + RunTransformsAndGenerateCode(ref state, output, ctx); + } + finally { + state.Dispose(); + } + } + + public override bool ShowMember(IMemberRef member) => CSharpDecompiler.ShowMember(member, showAllMembers, GetDecompilerSettings()); + + VBFormattingOptions CreateVBFormattingOptions(DecompilerSettings settings) => + new VBFormattingOptions() { + NumberFormatter = ICSharpCode.NRefactory.NumberFormatter.GetVBInstance(hex: settings.HexadecimalNumbers, upper: true), + }; + + void RunTransformsAndGenerateCode(ref BuilderState state, IDecompilerOutput output, DecompilationContext ctx, IAstTransform? additionalTransform = null) { + var astBuilder = state.AstBuilder; + astBuilder.RunTransformations(transformAbortCondition); + if (additionalTransform is not null) { + additionalTransform.Run(astBuilder.SyntaxTree); + } + var settings = GetDecompilerSettings(); + CSharpDecompiler.AddXmlDocumentation(ref state, settings, astBuilder); + var csharpUnit = astBuilder.SyntaxTree; + csharpUnit.AcceptVisitor(new ICSharpCode.NRefactory.CSharp.InsertParenthesesVisitor() { InsertParenthesesForReadability = true }); + var unit = csharpUnit.AcceptVisitor(new CSharpToVBConverterVisitor(state.AstBuilder.Context.CurrentModule, new ILSpyEnvironmentProvider(state.State.XmlDoc_StringBuilder)), null); + var outputFormatter = new VBTextOutputFormatter(output, astBuilder.Context); + var formattingPolicy = CreateVBFormattingOptions(settings); + unit.AcceptVisitor(new OutputVisitor(outputFormatter, formattingPolicy), null); + } + + BuilderState CreateAstBuilder(DecompilationContext ctx, DecompilerSettings settings, ModuleDef? currentModule = null, TypeDef? currentType = null, bool isSingleMember = false) { + if (currentModule is null) + currentModule = currentType?.Module; + settings = settings.Clone(); + if (isSingleMember) + settings.UsingDeclarations = false; + settings.IntroduceIncrementAndDecrement = false; + settings.MakeAssignmentExpressions = false; + settings.QueryExpressions = false; + settings.AlwaysGenerateExceptionVariableForCatchBlocksUnlessTypeIsObject = true; + var cache = ctx.GetOrCreate(createBuilderCache); + var state = new BuilderState(ctx, cache, MetadataTextColorProvider); + state.AstBuilder.Context.CurrentModule = currentModule; + state.AstBuilder.Context.CancellationToken = ctx.CancellationToken; + state.AstBuilder.Context.CurrentType = currentType; + state.AstBuilder.Context.Settings = settings; + return state; + } + + protected override void FormatTypeName(IDecompilerOutput output, TypeDef type) { + if (type is null) + throw new ArgumentNullException(nameof(type)); + + TypeToString(output, ConvertTypeOptions.DoNotUsePrimitiveTypeNames | ConvertTypeOptions.IncludeTypeParameterDefinitions | ConvertTypeOptions.DoNotIncludeEnclosingType, type); + } + + protected override void TypeToString(IDecompilerOutput output, ITypeDefOrRef? type, bool includeNamespace, IHasCustomAttribute? typeAttributes = null) { + ConvertTypeOptions options = ConvertTypeOptions.IncludeTypeParameterDefinitions; + if (includeNamespace) + options |= ConvertTypeOptions.IncludeNamespace; + + TypeToString(output, options, type, typeAttributes); + } + + void TypeToString(IDecompilerOutput output, ConvertTypeOptions options, ITypeDefOrRef? type, IHasCustomAttribute? typeAttributes = null) { + if (type is null) + return; + var envProvider = new ILSpyEnvironmentProvider(); + var converter = new CSharpToVBConverterVisitor(type.Module, envProvider); + var astType = AstBuilder.ConvertType(type, new StringBuilder(), typeAttributes, options); + + if (type.TryGetByRefSig() is not null) { + output.Write("ByRef", BoxedTextColor.Keyword); + output.Write(" ", BoxedTextColor.Text); + if (astType is ICSharpCode.NRefactory.CSharp.ComposedType && ((ICSharpCode.NRefactory.CSharp.ComposedType)astType).PointerRank > 0) + ((ICSharpCode.NRefactory.CSharp.ComposedType)astType).PointerRank--; + } + + var vbAstType = astType.AcceptVisitor(converter, null); + var settings = GetDecompilerSettings(); + var ctx = new DecompilerContext(settings.SettingsVersion, type.Module, MetadataTextColorProvider); + vbAstType.AcceptVisitor(new OutputVisitor(new VBTextOutputFormatter(output, ctx), CreateVBFormattingOptions(settings)), null); + } + + public override bool CanDecompile(DecompilationType decompilationType) { + switch (decompilationType) { + case DecompilationType.PartialType: + case DecompilationType.AssemblyInfo: + case DecompilationType.TypeMethods: + return true; + } + return base.CanDecompile(decompilationType); + } + + public override void Decompile(DecompilationType decompilationType, object data) { + switch (decompilationType) { + case DecompilationType.PartialType: + DecompilePartial((DecompilePartialType)data); + return; + case DecompilationType.AssemblyInfo: + DecompileAssemblyInfo((DecompileAssemblyInfo)data); + return; + case DecompilationType.TypeMethods: + DecompileTypeMethods((DecompileTypeMethods)data); + return; + } + base.Decompile(decompilationType, data); + } + + void DecompilePartial(DecompilePartialType info) { + var state = CreateAstBuilder(info.Context, CSharpDecompiler.CreateDecompilerSettings(GetDecompilerSettings(), info.UseUsingDeclarations), currentType: info.Type); + try { + state.AstBuilder.AddType(info.Type); + RunTransformsAndGenerateCode(ref state, info.Output, info.Context, new DecompilePartialTransform(info.Type, info.Definitions, info.ShowDefinitions, info.AddPartialKeyword, info.InterfacesToRemove)); + } + finally { + state.Dispose(); + } + } + + void DecompileAssemblyInfo(DecompileAssemblyInfo info) { + var state = CreateAstBuilder(info.Context, GetDecompilerSettings(), currentModule: info.Module); + try { + state.AstBuilder.AddAssembly(info.Module, true, info.Module.IsManifestModule, true); + RunTransformsAndGenerateCode(ref state, info.Output, info.Context, info.KeepAllAttributes ? null : new AssemblyInfoTransform()); + } + finally { + state.Dispose(); + } + } + + void DecompileTypeMethods(DecompileTypeMethods info) { + var state = CreateAstBuilder(info.Context, CSharpDecompiler.CreateDecompilerSettings_DecompileTypeMethods(GetDecompilerSettings(), !info.DecompileHidden, info.ShowAll), currentType: info.Type); + try { + state.AstBuilder.GetDecompiledBodyKind = (builder, method) => CSharpDecompiler.GetDecompiledBodyKind(info, builder, method); + state.AstBuilder.AddType(info.Type); + RunTransformsAndGenerateCode(ref state, info.Output, info.Context, new DecompileTypeMethodsTransform(info.Types, info.Methods, !info.DecompileHidden, info.ShowAll)); + } + finally { + state.Dispose(); + } + } + + public override void WriteToolTip(ITextColorWriter output, IMemberRef member, IHasCustomAttribute? typeAttributes) => + new VisualBasicFormatter(output, DefaultFormatterOptions, null).WriteToolTip(member); + public override void WriteToolTip(ITextColorWriter output, ISourceVariable variable) => + new VisualBasicFormatter(output, DefaultFormatterOptions, null).WriteToolTip(variable); + public override void WriteNamespaceToolTip(ITextColorWriter output, string? @namespace) => + new VisualBasicFormatter(output, DefaultFormatterOptions, null).WriteNamespaceToolTip(@namespace); + public override void Write(ITextColorWriter output, IMemberRef member, FormatterOptions flags) => + new VisualBasicFormatter(output, flags, null).Write(member); + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/VisualBasic/VBTextOutputFormatter.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/VisualBasic/VBTextOutputFormatter.cs new file mode 100644 index 0000000..bb0d4d3 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/VisualBasic/VBTextOutputFormatter.cs @@ -0,0 +1,374 @@ +// 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 System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using dnlib.DotNet; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Text; +using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.ILAst; +using ICSharpCode.NRefactory.VB; +using ICSharpCode.NRefactory.VB.Ast; +using CSharp2 = ICSharpCode.NRefactory.CSharp; + +namespace dnSpy.Decompiler.ILSpy.Core.VisualBasic { + sealed class VBTextOutputFormatter : IOutputFormatter { + readonly IDecompilerOutput output; + readonly DecompilerContext context; + readonly Stack nodeStack = new Stack(); + + public VBTextOutputFormatter(IDecompilerOutput output, DecompilerContext context) { + this.output = output ?? throw new ArgumentNullException(nameof(output)); + this.context = context ?? throw new ArgumentNullException(nameof(context)); + } + + MethodDebugInfoBuilder? currentMethodDebugInfoBuilder; + Stack parentMethodDebugInfoBuilder = new Stack(); + List>>? multiMappings; + + public void StartNode(AstNode node) { + nodeStack.Push(node); + + MethodDebugInfoBuilder mapping = node.Annotation(); + if (mapping is not null) { + parentMethodDebugInfoBuilder.Push(currentMethodDebugInfoBuilder); + currentMethodDebugInfoBuilder = mapping; + } + // For ctor/cctor field initializers + var mms = node.Annotation>>>(); + if (mms is not null) { + Debug2.Assert(multiMappings is null); + multiMappings = mms; + } + } + + public void EndNode(AstNode node) { + if (nodeStack.Pop() != node) + throw new InvalidOperationException(); + + if (node.Annotation() is not null) { + Debug2.Assert(currentMethodDebugInfoBuilder is not null); + if (context.CalculateILSpans) { + foreach (var ns in context.UsingNamespaces) + currentMethodDebugInfoBuilder.Scope.Imports.Add(ImportInfo.CreateNamespace(ns)); + } + output.AddDebugInfo(currentMethodDebugInfoBuilder.Create()); + currentMethodDebugInfoBuilder = parentMethodDebugInfoBuilder.Pop(); + } + var mms = node.Annotation>>>(); + if (mms is not null) { + Debug.Assert(mms == multiMappings); + if (mms == multiMappings) { + foreach (var mm in mms) + output.AddDebugInfo(mm.Item1.Create()); + multiMappings = null; + } + } + } + + public void WriteIdentifier(string identifier, object data, object extraData) { + var definition = GetCurrentDefinition(); + if (definition is not null) { + output.Write(IdentifierEscaper.Escape(identifier), definition, DecompilerReferenceFlags.Definition, data); + return; + } + + var memberRef = GetCurrentMemberReference() ?? (object?)(extraData as NamespaceReference); + if (memberRef is not null) { + output.Write(IdentifierEscaper.Escape(identifier), memberRef, DecompilerReferenceFlags.None, data); + return; + } + + definition = GetCurrentLocalDefinition(); + if (definition is not null) { + output.Write(IdentifierEscaper.Escape(identifier), definition, DecompilerReferenceFlags.Local | DecompilerReferenceFlags.Definition, data); + return; + } + + memberRef = GetCurrentLocalReference(); + if (memberRef is not null) { + output.Write(IdentifierEscaper.Escape(identifier), memberRef, DecompilerReferenceFlags.Local, data); + return; + } + + output.Write(IdentifierEscaper.Escape(identifier), data); + } + + IMemberRef? GetCurrentMemberReference() { + AstNode node = nodeStack.Peek(); + if (node.Annotation() is not null) + return null; + if (node.Role == AstNode.Roles.Type && node.Parent is ObjectCreationExpression) + node = node.Parent; + var memberRef = node.Annotation(); + if (memberRef is null && node is Identifier) { + node = node.Parent ?? node; + memberRef = node.Annotation(); + } + if (memberRef is null && node.Role == AstNode.Roles.TargetExpression && (node.Parent is InvocationExpression || node.Parent is ObjectCreationExpression)) { + memberRef = node.Parent.Annotation(); + } + return memberRef; + } + + object? GetCurrentLocalReference() { + AstNode node = nodeStack.Peek(); + ILVariable variable = node.Annotation(); + if (variable is null && node.Parent is IdentifierExpression) + variable = node.Parent.Annotation(); + if (variable is not null) + return variable.GetTextReferenceObject(); + var lbl = (node.Parent?.Parent as GoToStatement)?.Label ?? (node.Parent?.Parent as LabelDeclarationStatement)?.Label; + if (lbl is not null) { + var method = nodeStack.Select(nd => nd.Annotation()).FirstOrDefault(mr => mr is not null && mr.IsMethod); + if (method is not null) + return method.ToString() + lbl; + } + return null; + } + + object? GetCurrentLocalDefinition() { + AstNode node = nodeStack.Peek(); + if (node is Identifier && node.Parent is CatchBlock) + node = node.Parent; + var parameterDef = node.Annotation(); + if (parameterDef is not null) + return parameterDef; + if (node is ParameterDeclaration) { + node = ((ParameterDeclaration)node).Name; + parameterDef = node.Annotation(); + if (parameterDef is not null) + return parameterDef; + } + + if (node is VariableIdentifier) { + var variable = ((VariableIdentifier)node).Name.Annotation(); + if (variable is not null) + return variable.GetTextReferenceObject(); + node = node.Parent ?? node; + } + if (node is VariableDeclaratorWithTypeAndInitializer || node is VariableInitializer || node is CatchBlock || node is ForEachStatement) { + var variable = node.Annotation(); + if (variable is not null) + return variable.GetTextReferenceObject(); + } + + if (node is LabelDeclarationStatement label) { + var method = nodeStack.Select(nd => nd.Annotation()).FirstOrDefault(mr => mr is not null && mr.IsMethod); + if (method is not null) + return method.ToString() + label.Label; + } + + + return null; + } + + object? GetCurrentDefinition() { + if (nodeStack is null || nodeStack.Count == 0) + return null; + + var node = nodeStack.Peek(); + if (node is ParameterDeclaration) + return null; + if (node is VariableIdentifier) + return ((VariableIdentifier)node).Name.Annotation(); + if (IsDefinition(node)) + return node.Annotation(); + + if (node is Identifier) { + node = node.Parent; + if (IsDefinition(node)) + return node.Annotation(); + } + + return null; + } + + public void WriteKeyword(string keyword) { + var memberRef = GetCurrentMemberReference(); + var node = nodeStack.Peek(); + if (memberRef is not null && (node is PrimitiveType || node is InstanceExpression)) + output.Write(keyword, memberRef, DecompilerReferenceFlags.None, BoxedTextColor.Keyword); + else if (memberRef is not null && (node is ConstructorDeclaration && keyword == "New")) + output.Write(keyword, memberRef, DecompilerReferenceFlags.Local | DecompilerReferenceFlags.Definition, BoxedTextColor.Keyword); + else if (memberRef is not null && (node is Accessor && (keyword == "Get" || keyword == "Set" || keyword == "AddHandler" || keyword == "RemoveHandler" || keyword == "RaiseEvent"))) { + if (canPrintAccessor) + output.Write(keyword, memberRef, DecompilerReferenceFlags.Local | DecompilerReferenceFlags.Definition, BoxedTextColor.Keyword); + else + output.Write(keyword, BoxedTextColor.Keyword); + canPrintAccessor = !canPrintAccessor; + } + else if (memberRef is not null && node is OperatorDeclaration && keyword == "Operator") + output.Write(keyword, memberRef, DecompilerReferenceFlags.Definition, BoxedTextColor.Keyword); + else + output.Write(keyword, BoxedTextColor.Keyword); + } + bool canPrintAccessor = true; + + public void WriteToken(string token, object data, object? reference) { + var memberRef = GetCurrentMemberReference(); + var node = nodeStack.Peek(); + + bool addRef = memberRef is not null && + (node is BinaryOperatorExpression || + node is UnaryOperatorExpression || + node is AssignmentExpression); + + // Add a ref to the method if it's a delegate call + if (!addRef && node is InvocationExpression && memberRef is IMethod) { + var md = Resolve(memberRef as IMethod); + if (md is not null && md.DeclaringType is not null && md.DeclaringType.IsDelegate) + addRef = true; + } + + if (addRef) + output.Write(token, memberRef, DecompilerReferenceFlags.None, data); + else if (reference is not null) + output.Write(token, reference, DecompilerReferenceFlags.Local | DecompilerReferenceFlags.Hidden | DecompilerReferenceFlags.NoFollow, data); + else + output.Write(token, data); + } + + static MethodDef? Resolve(IMethod? method) { + if (method is MethodSpec) + method = ((MethodSpec)method).Method; + if (method is MemberRef) + return ((MemberRef)method).ResolveMethod(); + else + return (MethodDef?)method; + } + + public void Space() => output.Write(" ", BoxedTextColor.Text); + public void Indent() => output.IncreaseIndent(); + public void Unindent() => output.DecreaseIndent(); + public void NewLine() => output.WriteLine(); + + public void WriteComment(bool isDocumentation, string content, CSharp2.CommentReference[] refs) { + if (isDocumentation) { + Debug2.Assert(refs is null); + output.Write("'''", BoxedTextColor.XmlDocCommentDelimiter); + output.WriteXmlDoc(content); + output.WriteLine(); + } + else { + output.Write("'", BoxedTextColor.Comment); + Write(content, refs); + output.WriteLine(); + } + } + + void Write(string content, CSharp2.CommentReference[] refs) + { + if (refs is null) { + output.Write(content, BoxedTextColor.Comment); + return; + } + + int offs = 0; + for (int i = 0; i < refs.Length; i++) { + var @ref = refs[i]; + var s = content.Substring(offs, @ref.Length); + offs += @ref.Length; + if (@ref.Reference is null) + output.Write(s, BoxedTextColor.Comment); + else + output.Write(s, @ref.Reference, @ref.IsLocal ? DecompilerReferenceFlags.Local : DecompilerReferenceFlags.None, BoxedTextColor.Comment); + } + Debug.Assert(offs == content.Length); + } + + static bool IsDefinition(AstNode node) => + node is FieldDeclaration || + node is ConstructorDeclaration || + node is EventDeclaration || + node is DelegateDeclaration || + node is OperatorDeclaration || + node is MemberDeclaration || + node is TypeDeclaration || + node is EnumDeclaration || + node is EnumMemberDeclaration || + node is TypeParameterDeclaration; + + class DebugState { + public List Nodes = new List(); + public List ExtraILSpans = new List(); + public int StartLocation; + } + readonly Stack debugStack = new Stack(); + public void DebugStart(AstNode node) => debugStack.Push(new DebugState { StartLocation = output.NextPosition }); + + public void DebugHidden(object hiddenILSpans) { + if (hiddenILSpans is IList list) { + if (debugStack.Count > 0) + debugStack.Peek().ExtraILSpans.AddRange(list); + } + } + + public void DebugExpression(AstNode node) { + if (debugStack.Count > 0) + debugStack.Peek().Nodes.Add(node); + } + + public void DebugEnd(AstNode node) { + var state = debugStack.Pop(); + if (currentMethodDebugInfoBuilder is not null) { + foreach (var ilSpan in ILSpan.OrderAndCompact(GetILSpans(state))) + currentMethodDebugInfoBuilder.Add(new SourceStatement(ilSpan, new TextSpan(state.StartLocation, output.NextPosition - state.StartLocation))); + } + else if (multiMappings is not null) { + foreach (var mm in multiMappings) { + foreach (var ilSpan in ILSpan.OrderAndCompact(mm.Item2)) + mm.Item1.Add(new SourceStatement(ilSpan, new TextSpan(state.StartLocation, output.NextPosition - state.StartLocation))); + } + } + } + + static IEnumerable GetILSpans(DebugState state) { + foreach (var node in state.Nodes) { + foreach (var ann in node.Annotations) { + var list = ann as IList; + if (list is null) + continue; + foreach (var ilSpan in list) + yield return ilSpan; + } + } + foreach (var ilSpan in state.ExtraILSpans) + yield return ilSpan; + } + + public void AddHighlightedKeywordReference(object reference, int start, int end) { + Debug2.Assert(reference is not null); + if (reference is not null) + output.AddSpanReference(reference, start, end, PredefinedSpanReferenceIds.HighlightRelatedKeywords); + } + + public int NextPosition => output.NextPosition; + + public void AddBracePair(int leftStart, int leftEnd, int rightStart, int rightEnd, CodeBracesRangeFlags flags) => + output.AddBracePair(TextSpan.FromBounds(leftStart, leftEnd), TextSpan.FromBounds(rightStart, rightEnd), flags); + + public void AddBlock(int start, int end, CodeBracesRangeFlags flags) => + output.AddBracePair(new TextSpan(start, 0), new TextSpan(end, 0), flags); + + public void AddLineSeparator(int position) => output.AddLineSeparator(position); + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/XmlDoc/AddXmlDocTransform.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/XmlDoc/AddXmlDocTransform.cs new file mode 100644 index 0000000..9a30b18 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/XmlDoc/AddXmlDocTransform.cs @@ -0,0 +1,64 @@ +// 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 System.Text; +using dnlib.DotNet; +using dnSpy.Contracts.Decompiler.XmlDoc; +using ICSharpCode.NRefactory.CSharp; + +namespace dnSpy.Decompiler.ILSpy.Core.XmlDoc { + /// + /// Adds XML documentation for member definitions. + /// + struct AddXmlDocTransform { + readonly StringBuilder stringBuilder; + + public AddXmlDocTransform(StringBuilder sb) => stringBuilder = sb; + + public void Run(AstNode node) { + if (node is EntityDeclaration) { + IMemberRef mr = node.Annotation(); + if (mr is not null && mr.Module is not null) { + var xmldoc = XmlDocLoader.LoadDocumentation(mr.Module); + if (xmldoc is not null) { + var doc = xmldoc.GetDocumentation(XmlDocKeyProvider.GetKey(mr, stringBuilder)); + if (!string2.IsNullOrEmpty(doc)) { + InsertXmlDocumentation(node, doc); + } + } + } + if (!(node is TypeDeclaration)) + return; // don't recurse into attributed nodes, except for type definitions + } + foreach (AstNode child in node.Children) + Run(child); + } + + void InsertXmlDocumentation(AstNode node, string doc) { + foreach (var info in new XmlDocLine(doc)) { + stringBuilder.Clear(); + if (info is not null) { + stringBuilder.Append(' '); + info.Value.WriteTo(stringBuilder); + } + node.Parent.InsertChildBefore(node, new Comment(stringBuilder.ToString(), CommentType.Documentation), Roles.Comment); + } + } + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/XmlDoc/StringLineIterator.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/XmlDoc/StringLineIterator.cs new file mode 100644 index 0000000..119ac17 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/XmlDoc/StringLineIterator.cs @@ -0,0 +1,91 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; + +namespace dnSpy.Decompiler.ILSpy.Core.XmlDoc { + struct SubStringInfo { + public readonly int Index; + public readonly int Length; + + public SubStringInfo(int index, int length) { + Index = index; + Length = length; + } + } + + struct StringLineIterator : IEnumerable, IEnumerator { + readonly string s; + int index; + readonly int end; + SubStringInfo info; + bool finished; + + public StringLineIterator(string s, int index, int length) { + this.s = s; + this.index = index; + end = index + length; + info = default; + finished = false; + } + + public StringLineIterator GetEnumerator() => this; + + IEnumerator IEnumerable.GetEnumerator() { + Debug.Fail("'this' was boxed"); + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() { + Debug.Fail("'this' was boxed"); + return GetEnumerator(); + } + + public SubStringInfo Current => info; + + object IEnumerator.Current { + get { Debug.Fail("'this' was boxed"); return info; } + } + + public void Dispose() { } + + public bool MoveNext() { + int newLineIndex = s.IndexOfAny(newLineChars, index, end - index); + if (newLineIndex < 0) { + if (finished) + return false; + info = new SubStringInfo(index, end - index); + finished = true; + return true; + } + int len = newLineIndex - index; + info = new SubStringInfo(index, len); + if (s[newLineIndex] == '\r' && newLineIndex + 1 < s.Length && s[newLineIndex + 1] == '\n') + newLineIndex++; + index = newLineIndex + 1; + return true; + } + static readonly char[] newLineChars = new char[] { '\r', '\n', '\u0085', '\u2028', '\u2029' }; + + public void Reset() => throw new NotImplementedException(); + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/XmlDoc/SubString.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/XmlDoc/SubString.cs new file mode 100644 index 0000000..8376cad --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/XmlDoc/SubString.cs @@ -0,0 +1,37 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System.Text; + +namespace dnSpy.Decompiler.ILSpy.Core.XmlDoc { + struct SubString { + public readonly string String; + public readonly int Index; + public readonly int Length; + + public SubString(string s, int index, int length) { + String = s; + Index = index; + Length = length; + } + + public override string ToString() => String.Substring(Index, Length); + public void WriteTo(StringBuilder sb) => sb.Append(String, Index, Length); + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/XmlDoc/XmlDocLine.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/XmlDoc/XmlDocLine.cs new file mode 100644 index 0000000..638b017 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/XmlDoc/XmlDocLine.cs @@ -0,0 +1,153 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; + +namespace dnSpy.Decompiler.ILSpy.Core.XmlDoc { + struct XmlDocLine : IEnumerable, IEnumerator { + readonly string s; + readonly int end; + SubString? current; + SubStringInfo? indent; + StringLineIterator iter; + int emptyLines; + + public XmlDocLine(string s) + : this(s, 0, s.Length) { + } + + public XmlDocLine(string s, int start, int length) { + this.s = s; + end = start + length; + current = null; + indent = null; + iter = new StringLineIterator(s, start, end - start); + emptyLines = 0; + } + + public XmlDocLine GetEnumerator() => this; + + IEnumerator IEnumerable.GetEnumerator() { + Debug.Fail("'this' was boxed"); + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() { + Debug.Fail("'this' was boxed"); + return GetEnumerator(); + } + + public SubString? Current => current; + + object? IEnumerator.Current { + get { Debug.Fail("'this' was boxed"); return current; } + } + + public void Dispose() { } + + public bool MoveNext() { + if (indent is null) { + for (;;) { + if (!iter.MoveNext()) + return false; + if (!IsWhiteSpace(s, iter.Current)) + break; + } + + indent = GetIndentation(s, iter.Current); + goto start2; + } + + if (emptyLines != 0) + goto start2; +start: + if (!iter.MoveNext()) + return false; +start2: + if (IsWhiteSpace(s, iter.Current)) { + emptyLines++; + goto start; + } + + if (emptyLines != 0) { + if (emptyLines != -1) { + emptyLines--; + if (emptyLines == 0) + emptyLines = -1; + current = null; + return true; + } + emptyLines = 0; + } + + Trim(out int index, out int end); + current = new SubString(s, index, end - index); + return true; + } + + void Trim(out int trimmedIndex, out int trimmedEnd) { + Debug2.Assert(indent is not null); + + int index = iter.Current.Index; + int end = index + iter.Current.Length; + if (indent.Value.Length > iter.Current.Length) { + trimmedIndex = index; + trimmedEnd = end; + return; + } + + int end2 = index + indent.Value.Length; + for (int i = index, j = indent.Value.Index; i < end2; i++, j++) { + if (s[i] != s[j]) { + trimmedIndex = index; + trimmedEnd = end; + return; + } + } + + trimmedIndex = index + indent.Value.Length; + trimmedEnd = end; + Debug.Assert(trimmedIndex <= trimmedEnd); + } + + SubStringInfo GetIndentation(string doc, SubStringInfo info) { + int end = info.Index + info.Length; + int i = info.Index; + for (; i < end; i++) { + if (!char.IsWhiteSpace(doc[i])) + break; + } + return new SubStringInfo(info.Index, i - info.Index); + } + + bool IsWhiteSpace(string doc, SubStringInfo info) { + int end = info.Index + info.Length; + for (int i = info.Index; i < end; i++) { + if (!char.IsWhiteSpace(doc[i])) + return false; + } + return true; + } + + public void Reset() => throw new NotImplementedException(); + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/dnSpy.Decompiler.ILSpy.Core.csproj b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/dnSpy.Decompiler.ILSpy.Core.csproj new file mode 100644 index 0000000..216eb44 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/dnSpy.Decompiler.ILSpy.Core.csproj @@ -0,0 +1,39 @@ + + + + + + Copyright 2011-2014 AlphaSierraPapa for the SharpDevelop Team + $(DnSpyAssemblyVersion) + $(DnSpyAssemblyInformationalVersion) + + True + ..\..\..\dnSpy.snk + enable + + + + + True + True + dnSpy.Decompiler.ILSpy.Core.Resources.resx + + + + + + PublicResXFileCodeGenerator + dnSpy.Decompiler.ILSpy.Core.Resources.Designer.cs + + + + + + + + + + + + + diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/CSharp/DecompilerCreator.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/CSharp/DecompilerCreator.cs new file mode 100644 index 0000000..a35e2f5 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/CSharp/DecompilerCreator.cs @@ -0,0 +1,36 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System.Collections.Generic; +using System.ComponentModel.Composition; +using dnSpy.Contracts.Decompiler; +using dnSpy.Decompiler.ILSpy.Core.CSharp; +using dnSpy.Decompiler.ILSpy.Core.Settings; + +namespace dnSpy.Decompiler.ILSpy.CSharp { + [Export(typeof(IDecompilerCreator))] + sealed class MyDecompilerCreator : IDecompilerCreator { + readonly DecompilerSettingsService decompilerSettingsService; + + [ImportingConstructor] + MyDecompilerCreator(DecompilerSettingsService decompilerSettingsService) => this.decompilerSettingsService = decompilerSettingsService; + + public IEnumerable Create() => new DecompilerProvider(decompilerSettingsService).Create(); + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/ContentTypeDefinitions.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/ContentTypeDefinitions.cs new file mode 100644 index 0000000..41100b5 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/ContentTypeDefinitions.cs @@ -0,0 +1,57 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System.ComponentModel.Composition; +using dnSpy.Contracts.Text; +using dnSpy.Decompiler.ILSpy.Core.Text; +using Microsoft.VisualStudio.Utilities; + +namespace dnSpy.Decompiler.ILSpy { + static class ContentTypeDefinitions { +#pragma warning disable CS0169 + [Export] + [Name(ContentTypesInternal.DecompilerILSpy)] + [BaseDefinition(ContentTypes.DecompiledCode)] + static readonly ContentTypeDefinition? DecompilerILSpyContentTypeDefinition; + + [Export] + [Name(ContentTypesInternal.CSharpILSpy)] + [BaseDefinition(ContentTypesInternal.DecompilerILSpy)] + [BaseDefinition(ContentTypes.CSharp)] + static readonly ContentTypeDefinition? CSharpILSpyContentTypeDefinition; + + [Export] + [Name(ContentTypesInternal.VisualBasicILSpy)] + [BaseDefinition(ContentTypesInternal.DecompilerILSpy)] + [BaseDefinition(ContentTypes.VisualBasic)] + static readonly ContentTypeDefinition? VisualBasicILSpyContentTypeDefinition; + + [Export] + [Name(ContentTypesInternal.ILILSpy)] + [BaseDefinition(ContentTypesInternal.DecompilerILSpy)] + [BaseDefinition(ContentTypes.IL)] + static readonly ContentTypeDefinition? ILILSpyContentTypeDefinition; + + [Export] + [Name(ContentTypesInternal.ILAstILSpy)] + [BaseDefinition(ContentTypesInternal.DecompilerILSpy)] + static readonly ContentTypeDefinition? ILAstILSpyContentTypeDefinition; +#pragma warning restore CS0169 + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/IL/DecompilerCreator.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/IL/DecompilerCreator.cs new file mode 100644 index 0000000..ea3dbac --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/IL/DecompilerCreator.cs @@ -0,0 +1,36 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System.Collections.Generic; +using System.ComponentModel.Composition; +using dnSpy.Contracts.Decompiler; +using dnSpy.Decompiler.ILSpy.Core.IL; +using dnSpy.Decompiler.ILSpy.Core.Settings; + +namespace dnSpy.Decompiler.ILSpy.IL { + [Export(typeof(IDecompilerCreator))] + sealed class MyDecompilerCreator : IDecompilerCreator { + readonly DecompilerSettingsService decompilerSettingsService; + + [ImportingConstructor] + MyDecompilerCreator(DecompilerSettingsService decompilerSettingsService) => this.decompilerSettingsService = decompilerSettingsService; + + public IEnumerable Create() => new DecompilerProvider(decompilerSettingsService).Create(); + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/IL/SimpleILPrinter.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/IL/SimpleILPrinter.cs new file mode 100644 index 0000000..543bbc1 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/IL/SimpleILPrinter.cs @@ -0,0 +1,36 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System.ComponentModel.Composition; +using dnlib.DotNet; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Text; +using dnSpy.Decompiler.ILSpy.Core.IL; +using ICSharpCode.Decompiler.Disassembler; + +namespace dnSpy.Decompiler.ILSpy.IL { + [Export(typeof(ISimpleILPrinter))] + sealed class SimpleILPrinter : ISimpleILPrinter { + double ISimpleILPrinter.Order => -100; + + bool ISimpleILPrinter.Write(IDecompilerOutput output, IMemberRef? member) => ILDecompilerUtils.Write(output, member); + void ISimpleILPrinter.Write(IDecompilerOutput output, MethodSig? sig) => output.Write(sig); + void ISimpleILPrinter.Write(IDecompilerOutput output, TypeSig? type) => type.WriteTo(output); + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/ILAst/DecompilerCreator.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/ILAst/DecompilerCreator.cs new file mode 100644 index 0000000..1db75a0 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/ILAst/DecompilerCreator.cs @@ -0,0 +1,36 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System.Collections.Generic; +using System.ComponentModel.Composition; +using dnSpy.Contracts.Decompiler; +using dnSpy.Decompiler.ILSpy.Core.ILAst; +using dnSpy.Decompiler.ILSpy.Core.Settings; + +namespace dnSpy.Decompiler.ILSpy.ILAst { + [Export(typeof(IDecompilerCreator))] + sealed class MyDecompilerCreator : IDecompilerCreator { + readonly DecompilerSettingsService decompilerSettingsService; + + [ImportingConstructor] + MyDecompilerCreator(DecompilerSettingsService decompilerSettingsService) => this.decompilerSettingsService = decompilerSettingsService; + + public IEnumerable Create() => new DecompilerProvider(decompilerSettingsService).Create(); + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.Designer.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.Designer.cs new file mode 100644 index 0000000..d98ac55 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.Designer.cs @@ -0,0 +1,117 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace dnSpy.Decompiler.ILSpy.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class dnSpy_Decompiler_ILSpy_Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal dnSpy_Decompiler_ILSpy_Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("dnSpy.Decompiler.ILSpy.Properties.dnSpy.Decompiler.ILSpy.Resources", typeof(dnSpy_Decompiler_ILSpy_Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Events. + /// + public static string DecompilationOrder_Events { + get { + return ResourceManager.GetString("DecompilationOrder_Events", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Fields. + /// + public static string DecompilationOrder_Fields { + get { + return ResourceManager.GetString("DecompilationOrder_Fields", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Methods. + /// + public static string DecompilationOrder_Methods { + get { + return ResourceManager.GetString("DecompilationOrder_Methods", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Nested Types. + /// + public static string DecompilationOrder_NestedTypes { + get { + return ResourceManager.GetString("DecompilationOrder_NestedTypes", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Properties. + /// + public static string DecompilationOrder_Properties { + get { + return ResourceManager.GetString("DecompilationOrder_Properties", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ILSpy decompiler. + /// + public static string Plugin_ShortDescription { + get { + return ResourceManager.GetString("Plugin_ShortDescription", resourceCulture); + } + } + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.cs.resx b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.cs.resx new file mode 100644 index 0000000..ade5e39 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.cs.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Události + + + Pole + + + Metody + + + Vnořené typy + + + Vlastnosti + + + ILSpy dekompilátor + + \ No newline at end of file diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.de.resx b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.de.resx new file mode 100644 index 0000000..7fa1295 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.de.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Ereignisse + + + Felder + + + Methoden + + + Geschachtelte Typen + + + Eigenschaften + + + ILSpy-Dekompilierer + + \ No newline at end of file diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.es-ES.resx b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.es-ES.resx new file mode 100644 index 0000000..d94a0e5 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.es-ES.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Eventos + + + Campos + + + Métodos + + + Tipos Anidados + + + Propiedades + + + Decompilador ILSpy + + \ No newline at end of file diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.fa.resx b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.fa.resx new file mode 100644 index 0000000..be68bd5 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.fa.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + رویدادها + + + Fieldها + + + Methodها + + + Nested Types + + + Propertieها + + + ILSpy دیکامپایلر + + \ No newline at end of file diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.fr.resx b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.fr.resx new file mode 100644 index 0000000..e40d876 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.fr.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Events + + + Champs + + + Méthodes + + + Types imbriqués + + + Propriétés + + + Décompilateur ILSpy + + \ No newline at end of file diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.hu.resx b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.hu.resx new file mode 100644 index 0000000..d7dc25b --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.hu.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Események + + + Mezők + + + Metódusok + + + Beágyazott típusok + + + Tulajdonságok + + + ILSpy decompiler + + diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.it.resx b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.it.resx new file mode 100644 index 0000000..4714c71 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.it.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Eventi + + + Campi + + + Metodi + + + Tipi nidificati + + + Proprietá + + + ILSpy decompilatore + + \ No newline at end of file diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.pt-BR.resx b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.pt-BR.resx new file mode 100644 index 0000000..7c7ce8a --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.pt-BR.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Eventos + + + Campos + + + Métodos + + + Tipos Aninhados + + + Propriedades + + + Descompilador ILSpy + + \ No newline at end of file diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.pt-PT.resx b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.pt-PT.resx new file mode 100644 index 0000000..8592c0a --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.pt-PT.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Eventos + + + Campos + + + Métodos + + + Tipos aninhados + + + Propriedades + + + Descompilador ILSpy + + \ No newline at end of file diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.resx b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.resx new file mode 100644 index 0000000..69f8afe --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Events + + + Fields + + + Methods + + + Nested Types + + + Properties + + + ILSpy decompiler + + \ No newline at end of file diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.ru.resx b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.ru.resx new file mode 100644 index 0000000..d2773ec --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.ru.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + События + + + Поля + + + Методы + + + Вложенные типы + + + Свойства + + + Декомпилятор ILSpy + + diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.tr.resx b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.tr.resx new file mode 100644 index 0000000..2b701f2 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.tr.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Olaylar + + + Alanlar + + + Metodlar + + + İçiçe Türler + + + Özellikler + + + ILSpy decompiler + + \ No newline at end of file diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.uk.resx b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.uk.resx new file mode 100644 index 0000000..745c3d9 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.uk.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Події + + + Поля + + + Методи + + + Вкладені Типи + + + Властивості + + + Декомпілятор ILSpy + + diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.zh-CN.resx b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.zh-CN.resx new file mode 100644 index 0000000..999d4d7 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Properties/dnSpy.Decompiler.ILSpy.Resources.zh-CN.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 事件 + + + 字段 + + + 方法 + + + 嵌套类 + + + 属性 + + + ILSpy 反编译器 + + \ No newline at end of file diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Settings/CSharpDecompilerSettingsPage.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Settings/CSharpDecompilerSettingsPage.cs new file mode 100644 index 0000000..3d648e3 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Settings/CSharpDecompilerSettingsPage.cs @@ -0,0 +1,202 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; +using System.Diagnostics; +using System.Linq; +using dnSpy.Contracts.MVVM; +using dnSpy.Contracts.Settings.Dialog; +using dnSpy.Decompiler.ILSpy.Properties; +using ICSharpCode.Decompiler; + +namespace dnSpy.Decompiler.ILSpy.Settings { + sealed class CSharpDecompilerSettingsPage : AppSettingsPage, IAppSettingsPage2 { + readonly DecompilerSettings _global_decompilerSettings; + readonly DecompilerSettings decompilerSettings; + + public override double Order => AppSettingsConstants.ORDER_DECOMPILER_SETTINGS_ILSPY_CSHARP; + public DecompilerSettings Settings => decompilerSettings; + public override Guid ParentGuid => new Guid(AppSettingsConstants.GUID_DECOMPILER); + public override Guid Guid => new Guid("8929CE8E-7E2C-4701-A8BA-42F70363872C"); + public override string Title => "C# / Visual Basic (ILSpy)"; + public override object? UIObject => this; + + public DecompilationObjectVM[] DecompilationObjectsArray => decompilationObjectVMs2; + readonly DecompilationObjectVM[] decompilationObjectVMs; + readonly DecompilationObjectVM[] decompilationObjectVMs2; + + public DecompilationObjectVM DecompilationObject0 { + get => decompilationObjectVMs[0]; + set => SetDecompilationObject(0, value); + } + + public DecompilationObjectVM DecompilationObject1 { + get => decompilationObjectVMs[1]; + set => SetDecompilationObject(1, value); + } + + public DecompilationObjectVM DecompilationObject2 { + get => decompilationObjectVMs[2]; + set => SetDecompilationObject(2, value); + } + + public DecompilationObjectVM DecompilationObject3 { + get => decompilationObjectVMs[3]; + set => SetDecompilationObject(3, value); + } + + public DecompilationObjectVM DecompilationObject4 { + get => decompilationObjectVMs[4]; + set => SetDecompilationObject(4, value); + } + + void SetDecompilationObject(int index, DecompilationObjectVM newValue) { + Debug2.Assert(newValue is not null); + if (newValue is null) + throw new ArgumentNullException(nameof(newValue)); + if (decompilationObjectVMs[index] == newValue) + return; + + int otherIndex = Array.IndexOf(decompilationObjectVMs, newValue); + Debug.Assert(otherIndex >= 0); + if (otherIndex >= 0) { + decompilationObjectVMs[otherIndex] = decompilationObjectVMs[index]; + decompilationObjectVMs[index] = newValue; + + OnPropertyChanged($"DecompilationObject{otherIndex}"); + } + OnPropertyChanged($"DecompilationObject{index}"); + } + + public CSharpDecompilerSettingsPage(DecompilerSettings decompilerSettings) { + _global_decompilerSettings = decompilerSettings; + this.decompilerSettings = decompilerSettings.Clone(); + + var defObjs = typeof(DecompilationObject).GetEnumValues().Cast().ToArray(); + decompilationObjectVMs = new DecompilationObjectVM[defObjs.Length]; + for (int i = 0; i < defObjs.Length; i++) + decompilationObjectVMs[i] = new DecompilationObjectVM(defObjs[i], ToString(defObjs[i])); + decompilationObjectVMs2 = decompilationObjectVMs.ToArray(); + + DecompilationObject0 = decompilationObjectVMs.First(a => a.Object == decompilerSettings.DecompilationObject0); + DecompilationObject1 = decompilationObjectVMs.First(a => a.Object == decompilerSettings.DecompilationObject1); + DecompilationObject2 = decompilationObjectVMs.First(a => a.Object == decompilerSettings.DecompilationObject2); + DecompilationObject3 = decompilationObjectVMs.First(a => a.Object == decompilerSettings.DecompilationObject3); + DecompilationObject4 = decompilationObjectVMs.First(a => a.Object == decompilerSettings.DecompilationObject4); + } + + static string ToString(DecompilationObject o) { + switch (o) { + case DecompilationObject.NestedTypes: return dnSpy_Decompiler_ILSpy_Resources.DecompilationOrder_NestedTypes; + case DecompilationObject.Fields: return dnSpy_Decompiler_ILSpy_Resources.DecompilationOrder_Fields; + case DecompilationObject.Events: return dnSpy_Decompiler_ILSpy_Resources.DecompilationOrder_Events; + case DecompilationObject.Properties: return dnSpy_Decompiler_ILSpy_Resources.DecompilationOrder_Properties; + case DecompilationObject.Methods: return dnSpy_Decompiler_ILSpy_Resources.DecompilationOrder_Methods; + default: + Debug.Fail("Shouldn't be here"); + return "???"; + } + } + + [Flags] + enum RefreshFlags { + ShowMember = 0x00000001, + ILAst = 0x00000002, + CSharp = 0x00000004, + VB = 0x00000008, + DecompileAll = ILAst | CSharp | VB, + } + + public override void OnApply() => throw new InvalidOperationException(); + public void OnApply(IAppRefreshSettings appRefreshSettings) { + RefreshFlags flags = 0; + var g = _global_decompilerSettings; + var d = decompilerSettings; + + d.DecompilationObject0 = DecompilationObject0.Object; + d.DecompilationObject1 = DecompilationObject1.Object; + d.DecompilationObject2 = DecompilationObject2.Object; + d.DecompilationObject3 = DecompilationObject3.Object; + d.DecompilationObject4 = DecompilationObject4.Object; + + if (g.AnonymousMethods != d.AnonymousMethods) flags |= RefreshFlags.ILAst | RefreshFlags.ShowMember; + if (g.ExpressionTrees != d.ExpressionTrees) flags |= RefreshFlags.ILAst; + if (g.YieldReturn != d.YieldReturn) flags |= RefreshFlags.ILAst | RefreshFlags.ShowMember; + if (g.AsyncAwait != d.AsyncAwait) flags |= RefreshFlags.ILAst | RefreshFlags.ShowMember; + if (g.AutomaticProperties != d.AutomaticProperties) flags |= RefreshFlags.CSharp | RefreshFlags.ShowMember; + if (g.AutomaticEvents != d.AutomaticEvents) flags |= RefreshFlags.CSharp | RefreshFlags.ShowMember; + if (g.UsingStatement != d.UsingStatement) flags |= RefreshFlags.CSharp; + if (g.ForEachStatement != d.ForEachStatement) flags |= RefreshFlags.CSharp; + if (g.LockStatement != d.LockStatement) flags |= RefreshFlags.CSharp; + if (g.SwitchStatementOnString != d.SwitchStatementOnString) flags |= RefreshFlags.CSharp | RefreshFlags.ShowMember; + if (g.UsingDeclarations != d.UsingDeclarations) flags |= RefreshFlags.CSharp; + if (g.QueryExpressions != d.QueryExpressions) flags |= RefreshFlags.CSharp; + if (g.FullyQualifyAmbiguousTypeNames != d.FullyQualifyAmbiguousTypeNames) flags |= RefreshFlags.CSharp; + if (g.FullyQualifyAllTypes != d.FullyQualifyAllTypes) flags |= RefreshFlags.CSharp; + if (g.UseDebugSymbols != d.UseDebugSymbols) flags |= RefreshFlags.DecompileAll; + if (g.ObjectOrCollectionInitializers != d.ObjectOrCollectionInitializers) flags |= RefreshFlags.ILAst; + if (g.ShowXmlDocumentation != d.ShowXmlDocumentation) flags |= RefreshFlags.DecompileAll; + if (g.RemoveEmptyDefaultConstructors != d.RemoveEmptyDefaultConstructors) flags |= RefreshFlags.CSharp; + if (g.IntroduceIncrementAndDecrement != d.IntroduceIncrementAndDecrement) flags |= RefreshFlags.ILAst; + if (g.MakeAssignmentExpressions != d.MakeAssignmentExpressions) flags |= RefreshFlags.ILAst; + if (g.AlwaysGenerateExceptionVariableForCatchBlocksUnlessTypeIsObject != d.AlwaysGenerateExceptionVariableForCatchBlocksUnlessTypeIsObject) flags |= RefreshFlags.ILAst; + if (g.ShowTokenAndRvaComments != d.ShowTokenAndRvaComments) flags |= RefreshFlags.DecompileAll; + if (g.DecompilationObject0 != d.DecompilationObject0) flags |= RefreshFlags.CSharp; + if (g.DecompilationObject1 != d.DecompilationObject1) flags |= RefreshFlags.CSharp; + if (g.DecompilationObject2 != d.DecompilationObject2) flags |= RefreshFlags.CSharp; + if (g.DecompilationObject3 != d.DecompilationObject3) flags |= RefreshFlags.CSharp; + if (g.DecompilationObject4 != d.DecompilationObject4) flags |= RefreshFlags.CSharp; + if (g.SortMembers != d.SortMembers) flags |= RefreshFlags.CSharp; + if (g.ForceShowAllMembers != d.ForceShowAllMembers) flags |= RefreshFlags.CSharp | RefreshFlags.ShowMember; + if (g.SortSystemUsingStatementsFirst != d.SortSystemUsingStatementsFirst) flags |= RefreshFlags.CSharp; + if (g.MaxArrayElements != d.MaxArrayElements) flags |= RefreshFlags.CSharp; + if (g.MaxStringLength != d.MaxStringLength) flags |= RefreshFlags.CSharp; + if (g.SortCustomAttributes != d.SortCustomAttributes) flags |= RefreshFlags.CSharp; + if (g.UseSourceCodeOrder != d.UseSourceCodeOrder) flags |= RefreshFlags.CSharp; + if (g.AllowFieldInitializers != d.AllowFieldInitializers) flags |= RefreshFlags.CSharp; + if (g.OneCustomAttributePerLine != d.OneCustomAttributePerLine) flags |= RefreshFlags.CSharp; + if (g.TypeAddInternalModifier != d.TypeAddInternalModifier) flags |= RefreshFlags.CSharp; + if (g.MemberAddPrivateModifier != d.MemberAddPrivateModifier) flags |= RefreshFlags.CSharp; + if (g.HexadecimalNumbers != d.HexadecimalNumbers) flags |= RefreshFlags.CSharp; + + if ((flags & RefreshFlags.ShowMember) != 0) + appRefreshSettings.Add(AppSettingsConstants.REFRESH_LANGUAGE_SHOWMEMBER); + if ((flags & RefreshFlags.ILAst) != 0) + appRefreshSettings.Add(SettingsConstants.REDECOMPILE_ILAST_ILSPY_CODE); + if ((flags & RefreshFlags.CSharp) != 0) + appRefreshSettings.Add(SettingsConstants.REDECOMPILE_CSHARP_ILSPY_CODE); + if ((flags & RefreshFlags.VB) != 0) + appRefreshSettings.Add(SettingsConstants.REDECOMPILE_VB_ILSPY_CODE); + + decompilerSettings.CopyTo(_global_decompilerSettings); + } + + public override string[]? GetSearchStrings() => DecompilationObjectsArray.Select(a => a.Text).ToArray(); + } + + sealed class DecompilationObjectVM : ViewModelBase { + public DecompilationObject Object { get; } + public string Text { get; } + + public DecompilationObjectVM(DecompilationObject decompilationObject, string text) { + Object = decompilationObject; + Text = text; + } + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Settings/DecompilerAppSettingsModifiedListener.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Settings/DecompilerAppSettingsModifiedListener.cs new file mode 100644 index 0000000..aa2a648 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Settings/DecompilerAppSettingsModifiedListener.cs @@ -0,0 +1,69 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Documents.Tabs; +using dnSpy.Contracts.Settings.Dialog; + +namespace dnSpy.Decompiler.ILSpy.Settings { + [ExportAppSettingsModifiedListener(Order = AppSettingsConstants.ORDER_LISTENER_DECOMPILER)] + sealed class DecompilerAppSettingsModifiedListener : IAppSettingsModifiedListener { + readonly IDocumentTabService documentTabService; + + [ImportingConstructor] + DecompilerAppSettingsModifiedListener(IDocumentTabService documentTabService) => this.documentTabService = documentTabService; + + public void OnSettingsModified(IAppRefreshSettings appRefreshSettings) { + bool refreshIL = appRefreshSettings.Has(SettingsConstants.REDISASSEMBLE_IL_ILSPY_CODE); + bool refreshILAst = appRefreshSettings.Has(SettingsConstants.REDECOMPILE_ILAST_ILSPY_CODE); + bool refreshCSharp = appRefreshSettings.Has(SettingsConstants.REDECOMPILE_CSHARP_ILSPY_CODE); + bool refreshVB = appRefreshSettings.Has(SettingsConstants.REDECOMPILE_VB_ILSPY_CODE); + if (refreshILAst) + refreshCSharp = refreshVB = true; + if (refreshCSharp) + refreshVB = true; + + if (refreshIL) + RefreshCode(); +#if DEBUG + if (refreshILAst) + RefreshCode(); +#endif + if (refreshCSharp) + RefreshCode(); + if (refreshVB) + RefreshCode(); + } + + IEnumerable<(IDocumentTab tab, IDecompiler decompiler)> DecompilerTabs { + get { + foreach (var tab in documentTabService.VisibleFirstTabs) { + var decompiler = (tab.Content as IDecompilerTabContent)?.Decompiler; + if (decompiler is not null) + yield return (tab, decompiler); + } + } + } + + void RefreshCode() => documentTabService.Refresh(DecompilerTabs.Where(t => t.decompiler is T).Select(a => a.tab).ToArray()); + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Settings/DecompilerSettingsImpl.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Settings/DecompilerSettingsImpl.cs new file mode 100644 index 0000000..859f3ec --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Settings/DecompilerSettingsImpl.cs @@ -0,0 +1,131 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; +using System.ComponentModel.Composition; +using dnSpy.Contracts.Settings; +using ICSharpCode.Decompiler; + +namespace dnSpy.Decompiler.ILSpy.Settings { + [Export] + sealed class DecompilerSettingsImpl : DecompilerSettings { + static readonly Guid SETTINGS_GUID = new Guid("6745457F-254B-4B7B-90F1-F948F0721C3B"); + + readonly ISettingsService settingsService; + + [ImportingConstructor] + DecompilerSettingsImpl(ISettingsService settingsService) { + this.settingsService = settingsService; + + disableSave = true; + var sect = settingsService.GetOrCreateSection(SETTINGS_GUID); + // Only read those settings that can be changed in the dialog box + DecompilationObject0 = sect.Attribute(nameof(DecompilationObject0)) ?? DecompilationObject0; + DecompilationObject1 = sect.Attribute(nameof(DecompilationObject1)) ?? DecompilationObject1; + DecompilationObject2 = sect.Attribute(nameof(DecompilationObject2)) ?? DecompilationObject2; + DecompilationObject3 = sect.Attribute(nameof(DecompilationObject3)) ?? DecompilationObject3; + DecompilationObject4 = sect.Attribute(nameof(DecompilationObject4)) ?? DecompilationObject4; + AnonymousMethods = sect.Attribute(nameof(AnonymousMethods)) ?? AnonymousMethods; + ExpressionTrees = sect.Attribute(nameof(ExpressionTrees)) ?? ExpressionTrees; + YieldReturn = sect.Attribute(nameof(YieldReturn)) ?? YieldReturn; + AsyncAwait = sect.Attribute(nameof(AsyncAwait)) ?? AsyncAwait; + //AutomaticProperties = sect.Attribute(nameof(AutomaticProperties)) ?? AutomaticProperties; + //AutomaticEvents = sect.Attribute(nameof(AutomaticEvents)) ?? AutomaticEvents; + //UsingStatement = sect.Attribute(nameof(UsingStatement)) ?? UsingStatement; + //ForEachStatement = sect.Attribute(nameof(ForEachStatement)) ?? ForEachStatement; + //LockStatement = sect.Attribute(nameof(LockStatement)) ?? LockStatement; + //SwitchStatementOnString = sect.Attribute(nameof(SwitchStatementOnString)) ?? SwitchStatementOnString; + //UsingDeclarations = sect.Attribute(nameof(UsingDeclarations)) ?? UsingDeclarations; + QueryExpressions = sect.Attribute(nameof(QueryExpressions)) ?? QueryExpressions; + FullyQualifyAmbiguousTypeNames = sect.Attribute(nameof(FullyQualifyAmbiguousTypeNames)) ?? FullyQualifyAmbiguousTypeNames; + FullyQualifyAllTypes = sect.Attribute(nameof(FullyQualifyAllTypes)) ?? FullyQualifyAllTypes; + UseDebugSymbols = sect.Attribute(nameof(UseDebugSymbols)) ?? UseDebugSymbols; + //ObjectOrCollectionInitializers = sect.Attribute(nameof(ObjectOrCollectionInitializers)) ?? ObjectOrCollectionInitializers; + ShowXmlDocumentation = sect.Attribute(nameof(ShowXmlDocumentation)) ?? ShowXmlDocumentation; + RemoveEmptyDefaultConstructors = sect.Attribute(nameof(RemoveEmptyDefaultConstructors)) ?? RemoveEmptyDefaultConstructors; + //IntroduceIncrementAndDecrement = sect.Attribute(nameof(IntroduceIncrementAndDecrement)) ?? IntroduceIncrementAndDecrement; + //MakeAssignmentExpressions = sect.Attribute(nameof(MakeAssignmentExpressions)) ?? MakeAssignmentExpressions; + //AlwaysGenerateExceptionVariableForCatchBlocksUnlessTypeIsObject = sect.Attribute(nameof(AlwaysGenerateExceptionVariableForCatchBlocksUnlessTypeIsObject)) ?? AlwaysGenerateExceptionVariableForCatchBlocksUnlessTypeIsObject; + ShowTokenAndRvaComments = sect.Attribute(nameof(ShowTokenAndRvaComments)) ?? ShowTokenAndRvaComments; + SortMembers = sect.Attribute(nameof(SortMembers)) ?? SortMembers; + ForceShowAllMembers = sect.Attribute(nameof(ForceShowAllMembers)) ?? ForceShowAllMembers; + SortSystemUsingStatementsFirst = sect.Attribute(nameof(SortSystemUsingStatementsFirst)) ?? SortSystemUsingStatementsFirst; + //MaxArrayElements = sect.Attribute(nameof(MaxArrayElements)) ?? MaxArrayElements; + SortCustomAttributes = sect.Attribute(nameof(SortCustomAttributes)) ?? SortCustomAttributes; + UseSourceCodeOrder = sect.Attribute(nameof(UseSourceCodeOrder)) ?? UseSourceCodeOrder; + AllowFieldInitializers = sect.Attribute(nameof(AllowFieldInitializers)) ?? AllowFieldInitializers; + OneCustomAttributePerLine = sect.Attribute(nameof(OneCustomAttributePerLine)) ?? OneCustomAttributePerLine; + TypeAddInternalModifier = sect.Attribute(nameof(TypeAddInternalModifier)) ?? TypeAddInternalModifier; + MemberAddPrivateModifier = sect.Attribute(nameof(MemberAddPrivateModifier)) ?? MemberAddPrivateModifier; + //RemoveNewDelegateClass = sect.Attribute(nameof(RemoveNewDelegateClass)) ?? RemoveNewDelegateClass; + HexadecimalNumbers = sect.Attribute(nameof(HexadecimalNumbers)) ?? HexadecimalNumbers; + //TODO: CSharpFormattingOptions + disableSave = false; + } + readonly bool disableSave; + + protected override void OnModified() { + if (disableSave) + return; + + var sect = settingsService.RecreateSection(SETTINGS_GUID); + // Only save those settings that can be changed in the dialog box + sect.Attribute(nameof(DecompilationObject0), DecompilationObject0); + sect.Attribute(nameof(DecompilationObject1), DecompilationObject1); + sect.Attribute(nameof(DecompilationObject2), DecompilationObject2); + sect.Attribute(nameof(DecompilationObject3), DecompilationObject3); + sect.Attribute(nameof(DecompilationObject4), DecompilationObject4); + sect.Attribute(nameof(AnonymousMethods), AnonymousMethods); + sect.Attribute(nameof(ExpressionTrees), ExpressionTrees); + sect.Attribute(nameof(YieldReturn), YieldReturn); + sect.Attribute(nameof(AsyncAwait), AsyncAwait); + //sect.Attribute(nameof(AutomaticProperties), AutomaticProperties); + //sect.Attribute(nameof(AutomaticEvents), AutomaticEvents); + //sect.Attribute(nameof(UsingStatement), UsingStatement); + //sect.Attribute(nameof(ForEachStatement), ForEachStatement); + //sect.Attribute(nameof(LockStatement), LockStatement); + //sect.Attribute(nameof(SwitchStatementOnString), SwitchStatementOnString); + //sect.Attribute(nameof(UsingDeclarations), UsingDeclarations); + sect.Attribute(nameof(QueryExpressions), QueryExpressions); + sect.Attribute(nameof(FullyQualifyAmbiguousTypeNames), FullyQualifyAmbiguousTypeNames); + sect.Attribute(nameof(FullyQualifyAllTypes), FullyQualifyAllTypes); + sect.Attribute(nameof(UseDebugSymbols), UseDebugSymbols); + //sect.Attribute(nameof(ObjectOrCollectionInitializers), ObjectOrCollectionInitializers); + sect.Attribute(nameof(ShowXmlDocumentation), ShowXmlDocumentation); + sect.Attribute(nameof(RemoveEmptyDefaultConstructors), RemoveEmptyDefaultConstructors); + //sect.Attribute(nameof(IntroduceIncrementAndDecrement), IntroduceIncrementAndDecrement); + //sect.Attribute(nameof(MakeAssignmentExpressions), MakeAssignmentExpressions); + //sect.Attribute(nameof(AlwaysGenerateExceptionVariableForCatchBlocksUnlessTypeIsObject), AlwaysGenerateExceptionVariableForCatchBlocksUnlessTypeIsObject); + sect.Attribute(nameof(ShowTokenAndRvaComments), ShowTokenAndRvaComments); + sect.Attribute(nameof(SortMembers), SortMembers); + sect.Attribute(nameof(ForceShowAllMembers), ForceShowAllMembers); + sect.Attribute(nameof(SortSystemUsingStatementsFirst), SortSystemUsingStatementsFirst); + //sect.Attribute(nameof(MaxArrayElements), MaxArrayElements); + sect.Attribute(nameof(SortCustomAttributes), SortCustomAttributes); + sect.Attribute(nameof(UseSourceCodeOrder), UseSourceCodeOrder); + sect.Attribute(nameof(AllowFieldInitializers), AllowFieldInitializers); + sect.Attribute(nameof(OneCustomAttributePerLine), OneCustomAttributePerLine); + sect.Attribute(nameof(TypeAddInternalModifier), TypeAddInternalModifier); + sect.Attribute(nameof(MemberAddPrivateModifier), MemberAddPrivateModifier); + //sect.Attribute(nameof(RemoveNewDelegateClass), RemoveNewDelegateClass); + sect.Attribute(nameof(HexadecimalNumbers), HexadecimalNumbers); + //TODO: CSharpFormattingOptions + } + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Settings/DecompilerSettingsPageProvider.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Settings/DecompilerSettingsPageProvider.cs new file mode 100644 index 0000000..b618bb8 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Settings/DecompilerSettingsPageProvider.cs @@ -0,0 +1,42 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System.Collections.Generic; +using System.ComponentModel.Composition; +using dnSpy.Contracts.Settings.Dialog; +using ICSharpCode.Decompiler; + +namespace dnSpy.Decompiler.ILSpy.Settings { + [Export(typeof(IAppSettingsPageProvider))] + sealed class DecompilerSettingsPageProvider : IAppSettingsPageProvider { + readonly DecompilerSettings decompilerSettings; + readonly ILSettingsImpl ilSettings; + + [ImportingConstructor] + DecompilerSettingsPageProvider(DecompilerSettingsImpl decompilerSettings, ILSettingsImpl ilSettings) { + this.decompilerSettings = decompilerSettings; + this.ilSettings = ilSettings; + } + + public IEnumerable Create() { + yield return new CSharpDecompilerSettingsPage(decompilerSettings); + yield return new ILDecompilerSettingsPage(ilSettings); + } + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Settings/DecompilerSettingsServiceImpl.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Settings/DecompilerSettingsServiceImpl.cs new file mode 100644 index 0000000..f18de28 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Settings/DecompilerSettingsServiceImpl.cs @@ -0,0 +1,32 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System.ComponentModel.Composition; +using dnSpy.Decompiler.ILSpy.Core.Settings; + +namespace dnSpy.Decompiler.ILSpy.Settings { + [Export(typeof(DecompilerSettingsService))] + sealed class DecompilerSettingsServiceImpl : DecompilerSettingsService { + [ImportingConstructor] + DecompilerSettingsServiceImpl(DecompilerSettingsImpl decompilerSettings, ILSettingsImpl ilSettings) { + CSharpVBDecompilerSettings = new CSharpVBDecompilerSettings(decompilerSettings); + ILDecompilerSettings = new ILDecompilerSettings(ilSettings); + } + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Settings/ILDecompilerSettingsPage.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Settings/ILDecompilerSettingsPage.cs new file mode 100644 index 0000000..12bf225 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Settings/ILDecompilerSettingsPage.cs @@ -0,0 +1,49 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; +using dnSpy.Contracts.Settings.Dialog; +using dnSpy.Decompiler.ILSpy.Core.Settings; + +namespace dnSpy.Decompiler.ILSpy.Settings { + sealed class ILDecompilerSettingsPage : AppSettingsPage, IAppSettingsPage2 { + readonly ILSettings _global_ilSettings; + readonly ILSettings ilSettings; + + public override double Order => AppSettingsConstants.ORDER_DECOMPILER_SETTINGS_ILSPY_IL; + public ILSettings Settings => ilSettings; + public override Guid ParentGuid => new Guid(AppSettingsConstants.GUID_DECOMPILER); + public override Guid Guid => new Guid("0F8FBD3F-01DA-4AF0-9316-B7B5C8901A74"); + public override string Title => "IL (ILSpy)"; + public override object? UIObject => this; + + public ILDecompilerSettingsPage(ILSettings ilSettings) { + _global_ilSettings = ilSettings; + this.ilSettings = ilSettings.Clone(); + } + + public override void OnApply() => throw new InvalidOperationException(); + public void OnApply(IAppRefreshSettings appRefreshSettings) { + if (!_global_ilSettings.Equals(ilSettings)) + appRefreshSettings.Add(SettingsConstants.REDISASSEMBLE_IL_ILSPY_CODE); + + ilSettings.CopyTo(_global_ilSettings); + } + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Settings/ILSettingsImpl.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Settings/ILSettingsImpl.cs new file mode 100644 index 0000000..9956c88 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Settings/ILSettingsImpl.cs @@ -0,0 +1,65 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; +using System.ComponentModel.Composition; +using dnSpy.Contracts.Settings; +using dnSpy.Decompiler.ILSpy.Core.Settings; + +namespace dnSpy.Decompiler.ILSpy.Settings { + [Export] + sealed class ILSettingsImpl : ILSettings { + static readonly Guid SETTINGS_GUID = new Guid("DD6752B1-5336-4601-A9B2-0879E18AE9F3"); + + readonly ISettingsService settingsService; + + [ImportingConstructor] + ILSettingsImpl(ISettingsService settingsService) { + this.settingsService = settingsService; + + disableSave = true; + var sect = settingsService.GetOrCreateSection(SETTINGS_GUID); + ShowILComments = sect.Attribute(nameof(ShowILComments)) ?? ShowILComments; + ShowXmlDocumentation = sect.Attribute(nameof(ShowXmlDocumentation)) ?? ShowXmlDocumentation; + ShowTokenAndRvaComments = sect.Attribute(nameof(ShowTokenAndRvaComments)) ?? ShowTokenAndRvaComments; + ShowILBytes = sect.Attribute(nameof(ShowILBytes)) ?? ShowILBytes; + SortMembers = sect.Attribute(nameof(SortMembers)) ?? SortMembers; + ShowPdbInfo = sect.Attribute(nameof(ShowPdbInfo)) ?? ShowPdbInfo; + MaxStringLength = sect.Attribute(nameof(MaxStringLength)) ?? MaxStringLength; + HexadecimalNumbers = sect.Attribute(nameof(HexadecimalNumbers)) ?? HexadecimalNumbers; + disableSave = false; + } + readonly bool disableSave; + + protected override void OnModified() { + if (disableSave) + return; + + var sect = settingsService.RecreateSection(SETTINGS_GUID); + sect.Attribute(nameof(ShowILComments), ShowILComments); + sect.Attribute(nameof(ShowXmlDocumentation), ShowXmlDocumentation); + sect.Attribute(nameof(ShowTokenAndRvaComments), ShowTokenAndRvaComments); + sect.Attribute(nameof(ShowILBytes), ShowILBytes); + sect.Attribute(nameof(SortMembers), SortMembers); + sect.Attribute(nameof(ShowPdbInfo), ShowPdbInfo); + sect.Attribute(nameof(MaxStringLength), MaxStringLength); + sect.Attribute(nameof(HexadecimalNumbers), HexadecimalNumbers); + } + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Settings/SettingsConstants.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Settings/SettingsConstants.cs new file mode 100644 index 0000000..4014cbf --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Settings/SettingsConstants.cs @@ -0,0 +1,44 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; + +namespace dnSpy.Decompiler.ILSpy.Settings { + static class SettingsConstants { + /// + /// Redisassemble IL (ILSpy) code + /// + public static readonly Guid REDISASSEMBLE_IL_ILSPY_CODE = new Guid("9F975119-FB7A-461D-BA17-09B82C7AE258"); + + /// + /// Redecompile ILAst (ILSpy) code + /// + public static readonly Guid REDECOMPILE_ILAST_ILSPY_CODE = new Guid("20D97DB7-EA2D-489D-820F-DCDCE3AB01D3"); + + /// + /// Redecompile C# (ILSpy) code + /// + public static readonly Guid REDECOMPILE_CSHARP_ILSPY_CODE = new Guid("00C8E462-E6E7-4D1F-8CE0-3385B34EA1FB"); + + /// + /// Redecompile VB (ILSpy) code + /// + public static readonly Guid REDECOMPILE_VB_ILSPY_CODE = new Guid("818B8011-851F-4868-9674-3AC3A7B0AEC6"); + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/TheExtension.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/TheExtension.cs new file mode 100644 index 0000000..823969b --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/TheExtension.cs @@ -0,0 +1,39 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System.Collections.Generic; +using dnSpy.Contracts.Extension; +using dnSpy.Decompiler.ILSpy.Properties; + +namespace dnSpy.Decompiler.ILSpy { + [ExportExtension] + sealed class TheExtension : IExtension { + public IEnumerable MergedResourceDictionaries { + get { yield return "Themes/wpf.styles.templates.xaml"; } + } + + public ExtensionInfo ExtensionInfo => new ExtensionInfo { + ShortDescription = dnSpy_Decompiler_ILSpy_Resources.Plugin_ShortDescription, + Copyright = "Copyright 2011-2014 AlphaSierraPapa for the SharpDevelop Team", + }; + + public void OnEvent(ExtensionEvent @event, object? obj) { + } + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Themes/wpf.styles.templates.xaml b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Themes/wpf.styles.templates.xaml new file mode 100644 index 0000000..4e34d64 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/Themes/wpf.styles.templates.xaml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/VisualBasic/DecompilerCreator.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/VisualBasic/DecompilerCreator.cs new file mode 100644 index 0000000..f37c41b --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/VisualBasic/DecompilerCreator.cs @@ -0,0 +1,36 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System.Collections.Generic; +using System.ComponentModel.Composition; +using dnSpy.Contracts.Decompiler; +using dnSpy.Decompiler.ILSpy.Core.Settings; +using dnSpy.Decompiler.ILSpy.Core.VisualBasic; + +namespace dnSpy.Decompiler.ILSpy.VisualBasic { + [Export(typeof(IDecompilerCreator))] + sealed class MyDecompilerCreator : IDecompilerCreator { + readonly DecompilerSettingsService decompilerSettingsService; + + [ImportingConstructor] + MyDecompilerCreator(DecompilerSettingsService decompilerSettingsService) => this.decompilerSettingsService = decompilerSettingsService; + + public IEnumerable Create() => new DecompilerProvider(decompilerSettingsService).Create(); + } +} diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/dnSpy.Decompiler.ILSpy.csproj b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/dnSpy.Decompiler.ILSpy.csproj new file mode 100644 index 0000000..db72cd0 --- /dev/null +++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/dnSpy.Decompiler.ILSpy.csproj @@ -0,0 +1,44 @@ + + + + + + $(DnSpyAssemblyCopyright) + $(DnSpyAssemblyVersion) + $(DnSpyAssemblyInformationalVersion) + + dnSpy.Decompiler.ILSpy.x + True + ..\..\..\dnSpy.snk + ..\..\..\dnSpy\dnSpy\bin\$(Configuration)\ + enable + true + + + + + True + True + dnSpy.Decompiler.ILSpy.Resources.resx + + + + + + PublicResXFileCodeGenerator + dnSpy.Decompiler.ILSpy.Resources.Designer.cs + + + + + + + + + + + + + + + diff --git a/Extensions/dnSpy.Analyzer/AnalyzerService.cs b/Extensions/dnSpy.Analyzer/AnalyzerService.cs new file mode 100644 index 0000000..ea108b2 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/AnalyzerService.cs @@ -0,0 +1,376 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.Composition; +using System.Diagnostics; +using System.Linq; +using System.Windows.Input; +using dnlib.DotNet; +using dnSpy.Analyzer.TreeNodes; +using dnSpy.Contracts.Controls; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Documents; +using dnSpy.Contracts.Documents.Tabs; +using dnSpy.Contracts.Documents.Tabs.DocViewer; +using dnSpy.Contracts.Images; +using dnSpy.Contracts.Menus; +using dnSpy.Contracts.MVVM; +using dnSpy.Contracts.Text; +using dnSpy.Contracts.TreeView; +using dnSpy.Contracts.TreeView.Text; +using Microsoft.VisualStudio.Text; + +namespace dnSpy.Analyzer { + interface IAnalyzerService { + /// + /// Gets the instance + /// + ITreeView TreeView { get; } + + /// + /// Called when it's been closed + /// + void OnClose(); + + /// + /// Adds if it hasn't been added and gives it focus. + /// + /// Node + void Add(AnalyzerTreeNodeData node); + + /// + /// Called by when its method + /// has been called. + /// + /// Activated node (it's the caller) + void OnActivated(AnalyzerTreeNodeData node); + + /// + /// Follows the reference + /// + /// Node + /// true to show it in a new tab + /// true to show the reference in a method body + void FollowNode(TreeNodeData node, bool newTab, bool? useCodeRef); + + /// + /// Returns true if can execute + /// + /// Node + /// true to show the reference in a method body + /// + bool CanFollowNode(TreeNodeData node, bool useCodeRef); + } + + [Export(typeof(IAnalyzerService))] + sealed class AnalyzerService : IAnalyzerService, ITreeViewListener { + static readonly Guid ANALYZER_TREEVIEW_GUID = new Guid("8981898A-1384-4B67-9577-3CB096195146"); + + public ITreeView TreeView { get; } + + sealed class GuidObjectsProvider : IGuidObjectsProvider { + readonly ITreeView treeView; + + public GuidObjectsProvider(ITreeView treeView) => this.treeView = treeView; + + public IEnumerable GetGuidObjects(GuidObjectsProviderArgs args) { + yield return new GuidObject(MenuConstants.GUIDOBJ_TREEVIEW_NODES_ARRAY_GUID, treeView.TopLevelSelection); + } + } + + readonly AnalyzerTreeNodeDataContext context; + readonly IDocumentTabService documentTabService; + + [ImportingConstructor] + AnalyzerService(IWpfCommandService wpfCommandService, IDocumentTabService documentTabService, ITreeViewService treeViewService, IMenuService menuService, IAnalyzerSettings analyzerSettings, IDotNetImageService dotNetImageService, IDecompilerService decompilerService, ITreeViewNodeTextElementProvider treeViewNodeTextElementProvider) { + this.documentTabService = documentTabService; + + context = new AnalyzerTreeNodeDataContext { + DotNetImageService = dotNetImageService, + Decompiler = decompilerService.Decompiler, + TreeViewNodeTextElementProvider = treeViewNodeTextElementProvider, + DocumentService = documentTabService.DocumentTreeView.DocumentService, + ShowToken = analyzerSettings.ShowToken, + SingleClickExpandsChildren = analyzerSettings.SingleClickExpandsChildren, + SyntaxHighlight = analyzerSettings.SyntaxHighlight, + AnalyzerService = this, + }; + + var options = new TreeViewOptions { + CanDragAndDrop = false, + TreeViewListener = this, + }; + TreeView = treeViewService.Create(ANALYZER_TREEVIEW_GUID, options); + context.TreeView = TreeView; + + documentTabService.DocumentTreeView.DocumentService.CollectionChanged += DocumentService_CollectionChanged; + documentTabService.DocumentModified += DocumentTabService_FileModified; + decompilerService.DecompilerChanged += DecompilerService_DecompilerChanged; + analyzerSettings.PropertyChanged += AnalyzerSettings_PropertyChanged; + + menuService.InitializeContextMenu(TreeView.UIObject, new Guid(MenuConstants.GUIDOBJ_ANALYZER_TREEVIEW_GUID), new GuidObjectsProvider(TreeView)); + wpfCommandService.Add(ControlConstants.GUID_ANALYZER_TREEVIEW, TreeView.UIObject); + var cmds = wpfCommandService.GetCommands(ControlConstants.GUID_ANALYZER_TREEVIEW); + var command = new RelayCommand(a => ActivateNode()); + cmds.Add(command, ModifierKeys.Control, Key.Enter); + cmds.Add(command, ModifierKeys.Shift, Key.Enter); + } + + void DocumentTabService_FileModified(object? sender, DocumentModifiedEventArgs e) { + AnalyzerTreeNodeData.HandleModelUpdated(TreeView.Root, e.Documents); + RefreshNodes(); + } + + void ActivateNode() { + var nodes = TreeView.TopLevelSelection; + var node = nodes.Length == 0 ? null : nodes[0] as TreeNodeData; + if (node is not null) + node.Activate(); + } + + void DocumentService_CollectionChanged(object? sender, NotifyDocumentCollectionChangedEventArgs e) { + switch (e.Type) { + case NotifyDocumentCollectionType.Clear: + ClearAll(); + break; + + case NotifyDocumentCollectionType.Add: + AnalyzerTreeNodeData.HandleAssemblyListChanged(TreeView.Root, Array.Empty(), e.Documents); + break; + + case NotifyDocumentCollectionType.Remove: + AnalyzerTreeNodeData.HandleAssemblyListChanged(TreeView.Root, e.Documents, Array.Empty()); + break; + + default: + break; + } + } + + void DecompilerService_DecompilerChanged(object? sender, EventArgs e) { + context.Decompiler = ((IDecompilerService)sender!).Decompiler; + RefreshNodes(); + } + + void AnalyzerSettings_PropertyChanged(object? sender, PropertyChangedEventArgs e) { + var analyzerSettings = (IAnalyzerSettings)sender!; + switch (e.PropertyName) { + case nameof(analyzerSettings.ShowToken): + context.ShowToken = analyzerSettings.ShowToken; + RefreshNodes(); + break; + + case nameof(analyzerSettings.SyntaxHighlight): + context.SyntaxHighlight = analyzerSettings.SyntaxHighlight; + RefreshNodes(); + break; + + case nameof(analyzerSettings.SingleClickExpandsChildren): + context.SingleClickExpandsChildren = analyzerSettings.SingleClickExpandsChildren; + break; + } + } + + void RefreshNodes() => TreeView.RefreshAllNodes(); + + void ITreeViewListener.OnEvent(ITreeView treeView, TreeViewListenerEventArgs e) { + if (e.Event == TreeViewListenerEvent.NodeCreated) { + Debug2.Assert(context is not null); + var node = (ITreeNode)e.Argument; + if (node.Data is AnalyzerTreeNodeData d) + d.Context = context; + return; + } + } + + void Cancel() => AnalyzerTreeNodeData.CancelSelfAndChildren(TreeView.Root.Data); + public void OnClose() => ClearAll(); + + void ClearAll() { + Cancel(); + TreeView.Root.Children.Clear(); + } + + public void Add(AnalyzerTreeNodeData node) { + if (node is EntityNode an) { + var found = TreeView.Root.DataChildren.OfType().FirstOrDefault(n => n.Member == an.Member); + if (found is not null) { + found.TreeNode.IsExpanded = true; + TreeView.SelectItems(new TreeNodeData[] { found }); + TreeView.Focus(); + return; + } + } + TreeView.Root.Children.Add(TreeView.Create(node)); + node.TreeNode.IsExpanded = true; + TreeView.SelectItems(new TreeNodeData[] { node }); + TreeView.Focus(); + } + + public void OnActivated(AnalyzerTreeNodeData node) { + if (node is null) + throw new ArgumentNullException(nameof(node)); + bool newTab = Keyboard.Modifiers == ModifierKeys.Control || Keyboard.Modifiers == ModifierKeys.Shift; + FollowNode(node, newTab, null); + } + + public void FollowNode(TreeNodeData node, bool newTab, bool? useCodeRef) { + var tokNode = node as IMDTokenNode; + var @ref = tokNode?.Reference; + + var entityNode = node as EntityNode; + var srcRef = entityNode?.SourceRef; + + bool code = useCodeRef ?? srcRef is not null; + if (code) { + if (srcRef is null) + return; + if (srcRef.Value.ILOffset is not null) { + documentTabService.FollowReference(srcRef.Value.Method, newTab, true, a => { + if (!a.HasMovedCaret && a.Success && srcRef is not null) + a.HasMovedCaret = GoTo(a.Tab, srcRef.Value.Method, srcRef.Value.ILOffset, srcRef.Value.Reference); + }); + } + else + documentTabService.FollowReference(srcRef.Value.Method, newTab); + } + else { + if (@ref is null) + return; + documentTabService.FollowReference(@ref, newTab); + } + } + + public bool CanFollowNode(TreeNodeData node, bool useCodeRef) { + var tokNode = node as IMDTokenNode; + var @ref = tokNode?.Reference; + + var entityNode = node as EntityNode; + var srcRef = entityNode?.SourceRef; + + if (useCodeRef) + return srcRef is not null; + return @ref is not null; + } + + bool GoTo(IDocumentTab tab, MethodDef method, uint? ilOffset, object? @ref) { + if (method is null || ilOffset is null) + return false; + var documentViewer = tab.TryGetDocumentViewer(); + if (documentViewer is null) + return false; + var methodDebugService = documentViewer.GetMethodDebugService(); + var methodStatement = methodDebugService.FindByCodeOffset(method, ilOffset.Value); + if (methodStatement is null) + return false; + + var textSpan = methodStatement.Value.Statement.TextSpan; + var loc = FindLocation(documentViewer.Content.ReferenceCollection.FindFrom(textSpan.Start), documentViewer.TextView.TextSnapshot, methodStatement.Value.Statement.TextSpan.End, @ref); + if (loc is null) + loc = textSpan.Start; + + documentViewer.MoveCaretToPosition(loc.Value); + return true; + } + + static int GetLineNumber(ITextSnapshot snapshot, int position) { + Debug.Assert((uint)position <= (uint)snapshot.Length); + if ((uint)position > (uint)snapshot.Length) + return int.MaxValue; + return snapshot.GetLineFromPosition(position).LineNumber; + } + + int? FindLocation(IEnumerable> refs, ITextSnapshot snapshot, int endPos, object? @ref) { + int lb = GetLineNumber(snapshot, endPos); + foreach (var info in refs) { + var la = GetLineNumber(snapshot, info.Span.Start); + int c = Compare(la, info.Span.Start, lb, endPos); + if (c > 0) + break; + if (RefEquals(@ref, info.Data.Reference)) + return info.Span.Start; + } + return null; + } + + static int Compare(int la, int ca, int lb, int cb) { + if (la > lb) + return 1; + if (la == lb) + return ca - cb; + return -1; + } + + static bool RefEquals(object? a, object? b) { + if (Equals(a, b)) + return true; + if (a is null || b is null) + return false; + + { + if (b is PropertyDef) { + var tmp = a; + a = b; + b = tmp; + } + if (b is EventDef) { + var tmp = a; + a = b; + b = tmp; + } + } + + const SigComparerOptions flags = SigComparerOptions.CompareDeclaringTypes | SigComparerOptions.PrivateScopeIsComparable; + + if (a is IType type) + return new SigComparer().Equals(type, b as IType); + + if (a is IMethod method && method.IsMethod) + return new SigComparer(flags).Equals(method, b as IMethod); + + if (a is IField field && field.IsField) + return new SigComparer(flags).Equals(field, b as IField); + + if (a is PropertyDef prop) { + if (new SigComparer(flags).Equals(prop, b as PropertyDef)) + return true; + var bm = b as IMethod; + return bm is not null && + (new SigComparer(flags).Equals(prop.GetMethod, bm) || + new SigComparer(flags).Equals(prop.SetMethod, bm)); + } + + if (a is EventDef evt) { + if (new SigComparer(flags).Equals(evt, b as EventDef)) + return true; + var bm = b as IMethod; + return bm is not null && + (new SigComparer(flags).Equals(evt.AddMethod, bm) || + new SigComparer(flags).Equals(evt.InvokeMethod, bm) || + new SigComparer(flags).Equals(evt.RemoveMethod, bm)); + } + + Debug.Fail("Shouldn't be here"); + return false; + } + } +} diff --git a/Extensions/dnSpy.Analyzer/AnalyzerSettings.cs b/Extensions/dnSpy.Analyzer/AnalyzerSettings.cs new file mode 100644 index 0000000..db972ab --- /dev/null +++ b/Extensions/dnSpy.Analyzer/AnalyzerSettings.cs @@ -0,0 +1,101 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; +using System.ComponentModel; +using System.ComponentModel.Composition; +using dnSpy.Contracts.MVVM; +using dnSpy.Contracts.Settings; + +namespace dnSpy.Analyzer { + interface IAnalyzerSettings : INotifyPropertyChanged { + bool SyntaxHighlight { get; } + bool ShowToken { get; } + bool SingleClickExpandsChildren { get; } + } + + class AnalyzerSettings : ViewModelBase, IAnalyzerSettings { + public bool SyntaxHighlight { + get => syntaxHighlight; + set { + if (syntaxHighlight != value) { + syntaxHighlight = value; + OnPropertyChanged(nameof(SyntaxHighlight)); + } + } + } + bool syntaxHighlight = true; + + public bool ShowToken { + get => showToken; + set { + if (showToken != value) { + showToken = value; + OnPropertyChanged(nameof(ShowToken)); + } + } + } + bool showToken = true; + + public bool SingleClickExpandsChildren { + get => singleClickExpandsChildren; + set { + if (singleClickExpandsChildren != value) { + singleClickExpandsChildren = value; + OnPropertyChanged(nameof(SingleClickExpandsChildren)); + } + } + } + bool singleClickExpandsChildren = true; + + public AnalyzerSettings Clone() => CopyTo(new AnalyzerSettings()); + + public AnalyzerSettings CopyTo(AnalyzerSettings other) { + other.SyntaxHighlight = SyntaxHighlight; + other.ShowToken = ShowToken; + other.SingleClickExpandsChildren = SingleClickExpandsChildren; + return other; + } + } + + [Export, Export(typeof(IAnalyzerSettings))] + sealed class AnalyzerSettingsImpl : AnalyzerSettings { + static readonly Guid SETTINGS_GUID = new Guid("0A9208EC-CFAB-41C2-82C6-FCDA44A8E684"); + + readonly ISettingsService settingsService; + + [ImportingConstructor] + AnalyzerSettingsImpl(ISettingsService settingsService) { + this.settingsService = settingsService; + + var sect = settingsService.GetOrCreateSection(SETTINGS_GUID); + SyntaxHighlight = sect.Attribute(nameof(SyntaxHighlight)) ?? SyntaxHighlight; + ShowToken = sect.Attribute(nameof(ShowToken)) ?? ShowToken; + SingleClickExpandsChildren = sect.Attribute(nameof(SingleClickExpandsChildren)) ?? SingleClickExpandsChildren; + PropertyChanged += AnalyzerSettingsImpl_PropertyChanged; + } + + void AnalyzerSettingsImpl_PropertyChanged(object? sender, PropertyChangedEventArgs e) { + var sect = settingsService.RecreateSection(SETTINGS_GUID); + sect.Attribute(nameof(SyntaxHighlight), SyntaxHighlight); + sect.Attribute(nameof(ShowToken), ShowToken); + sect.Attribute(nameof(SingleClickExpandsChildren), SingleClickExpandsChildren); + } + } +} diff --git a/Extensions/dnSpy.Analyzer/AnalyzerToolWindowContent.cs b/Extensions/dnSpy.Analyzer/AnalyzerToolWindowContent.cs new file mode 100644 index 0000000..caeedf5 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/AnalyzerToolWindowContent.cs @@ -0,0 +1,69 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Windows; +using dnSpy.Analyzer.Properties; +using dnSpy.Contracts.Controls; +using dnSpy.Contracts.ToolWindows; +using dnSpy.Contracts.ToolWindows.App; + +namespace dnSpy.Analyzer { + [Export(typeof(IToolWindowContentProvider))] + sealed class AnalyzerToolWindowContentProvider : IToolWindowContentProvider { + readonly Lazy analyzerService; + + public AnalyzerToolWindowContent DocumentTreeViewWindowContent => analyzerToolWindowContent ??= new AnalyzerToolWindowContent(analyzerService); + AnalyzerToolWindowContent? analyzerToolWindowContent; + + [ImportingConstructor] + AnalyzerToolWindowContentProvider(Lazy analyzerService) => this.analyzerService = analyzerService; + + public IEnumerable ContentInfos { + get { yield return new ToolWindowContentInfo(AnalyzerToolWindowContent.THE_GUID, AnalyzerToolWindowContent.DEFAULT_LOCATION, AppToolWindowConstants.DEFAULT_CONTENT_ORDER_BOTTOM_ANALYZER, false); } + } + + public ToolWindowContent? GetOrCreate(Guid guid) => guid == AnalyzerToolWindowContent.THE_GUID ? DocumentTreeViewWindowContent : null; + } + + sealed class AnalyzerToolWindowContent : ToolWindowContent, IFocusable { + public static readonly Guid THE_GUID = new Guid("5827D693-A5DF-4D65-B1F8-ACF249508A96"); + public const AppToolWindowLocation DEFAULT_LOCATION = AppToolWindowLocation.DefaultHorizontal; + + public override IInputElement? FocusedElement => null; + public override FrameworkElement? ZoomElement => analyzerService.Value.TreeView.UIObject; + public override Guid Guid => THE_GUID; + public override string Title => dnSpy_Analyzer_Resources.AnalyzerWindowTitle; + public override object? UIObject => analyzerService.Value.TreeView.UIObject; + public bool CanFocus => true; + + readonly Lazy analyzerService; + + public AnalyzerToolWindowContent(Lazy analyzerService) => this.analyzerService = analyzerService; + + public override void OnVisibilityChanged(ToolWindowContentVisibilityEvent visEvent) { + if (visEvent == ToolWindowContentVisibilityEvent.Removed) + analyzerService.Value.OnClose(); + } + + public void Focus() => analyzerService.Value.TreeView.Focus(); + } +} diff --git a/Extensions/dnSpy.Analyzer/AnalyzerTreeNodeDataContext.cs b/Extensions/dnSpy.Analyzer/AnalyzerTreeNodeDataContext.cs new file mode 100644 index 0000000..52f964c --- /dev/null +++ b/Extensions/dnSpy.Analyzer/AnalyzerTreeNodeDataContext.cs @@ -0,0 +1,41 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using dnSpy.Analyzer.TreeNodes; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Documents; +using dnSpy.Contracts.Images; +using dnSpy.Contracts.TreeView; +using dnSpy.Contracts.TreeView.Text; + +namespace dnSpy.Analyzer { + sealed class AnalyzerTreeNodeDataContext : IAnalyzerTreeNodeDataContext { +#pragma warning disable CS8618 // Non-nullable field is uninitialized. + public IDotNetImageService DotNetImageService { get; set; } + public ITreeView TreeView { get; set; } + public IDecompiler Decompiler { get; set; } + public ITreeViewNodeTextElementProvider TreeViewNodeTextElementProvider { get; set; } + public IDsDocumentService DocumentService { get; set; } + public IAnalyzerService AnalyzerService { get; set; } + public bool ShowToken { get; set; } + public bool SingleClickExpandsChildren { get; set; } + public bool SyntaxHighlight { get; set; } +#pragma warning restore CS8618 // Non-nullable field is uninitialized. + } +} diff --git a/Extensions/dnSpy.Analyzer/Commands.cs b/Extensions/dnSpy.Analyzer/Commands.cs new file mode 100644 index 0000000..2e78ee9 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/Commands.cs @@ -0,0 +1,210 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Runtime.InteropServices; +using System.Text; +using System.Windows; +using System.Windows.Input; +using dnSpy.Contracts.Controls; +using dnSpy.Contracts.Extension; +using dnSpy.Contracts.Images; +using dnSpy.Contracts.Menus; +using dnSpy.Contracts.TreeView; + +namespace dnSpy.Analyzer { + abstract class OpenReferenceCtxMenuCommandBase : MenuItemBase { + readonly Lazy analyzerService; + readonly bool newTab; + readonly bool useCodeRef; + + protected OpenReferenceCtxMenuCommandBase(Lazy analyzerService, bool newTab, bool useCodeRef) { + this.analyzerService = analyzerService; + this.newTab = newTab; + this.useCodeRef = useCodeRef; + } + + public override void Execute(IMenuItemContext context) { + var @ref = GetReference(context); + if (@ref is null) + return; + analyzerService.Value.FollowNode(@ref, newTab, useCodeRef); + } + + public override bool IsVisible(IMenuItemContext context) => GetReference(context) is not null; + + TreeNodeData? GetReference(IMenuItemContext context) { + if (context.CreatorObject.Guid != new Guid(MenuConstants.GUIDOBJ_ANALYZER_TREEVIEW_GUID)) + return null; + + var nodes = context.Find(); + if (nodes is null || nodes.Length != 1) + return null; + + if (nodes[0] is IMDTokenNode tokenNode && tokenNode.Reference is not null) { + if (!analyzerService.Value.CanFollowNode(nodes[0], useCodeRef)) + return null; + return nodes[0]; + } + + return null; + } + } + + [ExportMenuItem(Header = "res:GoToReferenceInCodeCommand", InputGestureText = "res:DoubleClick", Group = MenuConstants.GROUP_CTX_ANALYZER_TABS, Order = 0)] + sealed class OpenReferenceInCodeCtxMenuCommand : OpenReferenceCtxMenuCommandBase { + [ImportingConstructor] + OpenReferenceInCodeCtxMenuCommand(Lazy analyzerService) + : base(analyzerService, false, true) { + } + } + + [ExportMenuItem(Header = "res:GoToReferenceInCodeNewTabCommand", InputGestureText = "res:ShiftDoubleClick", Group = MenuConstants.GROUP_CTX_ANALYZER_TABS, Order = 10)] + sealed class OpenReferenceInCodeNewTabCtxMenuCommand : OpenReferenceCtxMenuCommandBase { + [ImportingConstructor] + OpenReferenceInCodeNewTabCtxMenuCommand(Lazy analyzerService) + : base(analyzerService, true, true) { + } + } + + [ExportMenuItem(Header = "res:GoToReferenceCommand", Group = MenuConstants.GROUP_CTX_ANALYZER_TABS, Order = 20)] + sealed class OpenReferenceCtxMenuCommand : OpenReferenceCtxMenuCommandBase { + [ImportingConstructor] + OpenReferenceCtxMenuCommand(Lazy analyzerService) + : base(analyzerService, false, false) { + } + } + + [ExportMenuItem(Header = "res:GoToReferenceNewTabCommand", Group = MenuConstants.GROUP_CTX_ANALYZER_TABS, Order = 30)] + sealed class OpenReferenceNewTabCtxMenuCommand : OpenReferenceCtxMenuCommandBase { + [ImportingConstructor] + OpenReferenceNewTabCtxMenuCommand(Lazy analyzerService) + : base(analyzerService, true, false) { + } + } + + [ExportAutoLoaded] + sealed class BreakpointsContentCommandLoader : IAutoLoaded { + [ImportingConstructor] + BreakpointsContentCommandLoader(IWpfCommandService wpfCommandService, Lazy analyzerService) { + var cmds = wpfCommandService.GetCommands(ControlConstants.GUID_ANALYZER_TREEVIEW); + cmds.Add(ApplicationCommands.Copy, + (s, e) => CopyCtxMenuCommand.ExecuteInternal(analyzerService), + (s, e) => e.CanExecute = CopyCtxMenuCommand.CanExecuteInternal(analyzerService)); + } + } + + [ExportMenuItem(Header = "res:CopyCommand", InputGestureText = "res:ShortCutKeyCtrlC", Icon = DsImagesAttribute.Copy, Group = MenuConstants.GROUP_CTX_ANALYZER_TOKENS, Order = -1)] + sealed class CopyCtxMenuCommand : MenuItemBase { + readonly Lazy analyzerService; + + [ImportingConstructor] + CopyCtxMenuCommand(Lazy analyzerService) => this.analyzerService = analyzerService; + + public override bool IsVisible(IMenuItemContext context) => context.CreatorObject.Guid == new Guid(MenuConstants.GUIDOBJ_ANALYZER_TREEVIEW_GUID); + public override bool IsEnabled(IMenuItemContext context) => CanExecuteInternal(analyzerService); + public override void Execute(IMenuItemContext context) => ExecuteInternal(analyzerService); + public static bool CanExecuteInternal(Lazy analyzerService) => analyzerService.Value.TreeView.SelectedItems.Length > 0; + + public static void ExecuteInternal(Lazy analyzerService) { + var items = analyzerService.Value.TreeView.SelectedItems; + var sb = new StringBuilder(); + int count = 0; + foreach (var t in GetNodes(analyzerService.Value.TreeView, items)) { + if (count > 0) + sb.Append(Environment.NewLine); + sb.Append(new string('\t', t.level)); + sb.Append(t.node.ToString()); + count++; + } + if (count > 1) + sb.Append(Environment.NewLine); + if (sb.Length > 0) { + try { + Clipboard.SetText(sb.ToString()); + } + catch (ExternalException) { } + } + } + + sealed class State { + public readonly int Level; + public int Index; + public readonly IList Nodes; + public State(ITreeNode node, int level) { + Level = level; + Nodes = node.Children; + } + } + + static IEnumerable<(int level, TreeNodeData node)> GetNodes(ITreeView treeView, IEnumerable nodes) { + var hash = new HashSet(nodes); + var stack = new Stack(); + stack.Push(new State(treeView.Root, 0)); + while (stack.Count > 0) { + var state = stack.Pop(); + if (state.Index >= state.Nodes.Count) + continue; + var child = state.Nodes[state.Index++]; + if (hash.Contains(child.Data)) + yield return (state.Level, child.Data); + stack.Push(state); + stack.Push(new State(child, state.Level + 1)); + } + } + } + + [ExportMenuItem(Header = "res:ShowMetadataTokensCommand", Group = MenuConstants.GROUP_CTX_ANALYZER_OPTIONS, Order = 0)] + sealed class ShowTokensCtxMenuCommand : MenuItemBase { + readonly AnalyzerSettingsImpl analyzerSettings; + + [ImportingConstructor] + ShowTokensCtxMenuCommand(AnalyzerSettingsImpl analyzerSettings) => this.analyzerSettings = analyzerSettings; + + public override bool IsVisible(IMenuItemContext context) => context.CreatorObject.Guid == new Guid(MenuConstants.GUIDOBJ_ANALYZER_TREEVIEW_GUID); + public override bool IsChecked(IMenuItemContext context) => analyzerSettings.ShowToken; + public override void Execute(IMenuItemContext context) => analyzerSettings.ShowToken = !analyzerSettings.ShowToken; + } + + [ExportMenuItem(Header = "res:SyntaxHighlightCommand", Group = MenuConstants.GROUP_CTX_ANALYZER_OPTIONS, Order = 10)] + sealed class SyntaxHighlightCtxMenuCommand : MenuItemBase { + readonly AnalyzerSettingsImpl analyzerSettings; + + [ImportingConstructor] + SyntaxHighlightCtxMenuCommand(AnalyzerSettingsImpl analyzerSettings) => this.analyzerSettings = analyzerSettings; + + public override bool IsVisible(IMenuItemContext context) => context.CreatorObject.Guid == new Guid(MenuConstants.GUIDOBJ_ANALYZER_TREEVIEW_GUID); + public override bool IsChecked(IMenuItemContext context) => analyzerSettings.SyntaxHighlight; + public override void Execute(IMenuItemContext context) => analyzerSettings.SyntaxHighlight = !analyzerSettings.SyntaxHighlight; + } + + [ExportMenuItem(Header = "res:SingleClickExpandNodes", Group = MenuConstants.GROUP_CTX_ANALYZER_OPTIONS, Order = 20)] + sealed class SingleClickExpandNodesCtxMenuCommand : MenuItemBase { + readonly AnalyzerSettingsImpl analyzerSettings; + + [ImportingConstructor] + SingleClickExpandNodesCtxMenuCommand(AnalyzerSettingsImpl analyzerSettings) => this.analyzerSettings = analyzerSettings; + + public override bool IsVisible(IMenuItemContext context) => context.CreatorObject.Guid == new Guid(MenuConstants.GUIDOBJ_ANALYZER_TREEVIEW_GUID); + public override bool IsChecked(IMenuItemContext context) => analyzerSettings.SingleClickExpandsChildren; + public override void Execute(IMenuItemContext context) => analyzerSettings.SingleClickExpandsChildren = !analyzerSettings.SingleClickExpandsChildren; + } +} diff --git a/Extensions/dnSpy.Analyzer/ContentTypeDefinitions.cs b/Extensions/dnSpy.Analyzer/ContentTypeDefinitions.cs new file mode 100644 index 0000000..2e2cd12 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/ContentTypeDefinitions.cs @@ -0,0 +1,33 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System.ComponentModel.Composition; +using dnSpy.Contracts.TreeView.Text; +using Microsoft.VisualStudio.Utilities; + +namespace dnSpy.Analyzer { + static class ContentTypeDefinitions { +#pragma warning disable CS0169 + [Export] + [Name(TreeViewContentTypes.TreeViewNodeAnalyzer)] + [BaseDefinition(TreeViewContentTypes.TreeViewNode)] + static readonly ContentTypeDefinition? TreeViewNodeAnalyzer; +#pragma warning restore CS0169 + } +} diff --git a/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.Designer.cs b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.Designer.cs new file mode 100644 index 0000000..a0f3a10 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.Designer.cs @@ -0,0 +1,342 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace dnSpy.Analyzer.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class dnSpy_Analyzer_Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal dnSpy_Analyzer_Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("dnSpy.Analyzer.Properties.dnSpy.Analyzer.Resources", typeof(dnSpy_Analyzer_Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Analy_ze. + /// + public static string AnalyzeCommand { + get { + return ResourceManager.GetString("AnalyzeCommand", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Analyzer. + /// + public static string AnalyzerWindowTitle { + get { + return ResourceManager.GetString("AnalyzerWindowTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Applied To. + /// + public static string AppliedToTreeNode { + get { + return ResourceManager.GetString("AppliedToTreeNode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Assigned By. + /// + public static string AssignedByTreeNode { + get { + return ResourceManager.GetString("AssignedByTreeNode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cop_y. + /// + public static string CopyCommand { + get { + return ResourceManager.GetString("CopyCommand", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Dbl Click. + /// + public static string DoubleClick { + get { + return ResourceManager.GetString("DoubleClick", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Exposed By. + /// + public static string ExposedByTreeNode { + get { + return ResourceManager.GetString("ExposedByTreeNode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Extension Methods. + /// + public static string ExtensionMethodsTreeNode { + get { + return ResourceManager.GetString("ExtensionMethodsTreeNode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Go to Reference. + /// + public static string GoToReferenceCommand { + get { + return ResourceManager.GetString("GoToReferenceCommand", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show Reference in Method. + /// + public static string GoToReferenceInCodeCommand { + get { + return ResourceManager.GetString("GoToReferenceInCodeCommand", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show Reference in Method (New _Tab). + /// + public static string GoToReferenceInCodeNewTabCommand { + get { + return ResourceManager.GetString("GoToReferenceInCodeNewTabCommand", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Go to Reference (New Ta_b). + /// + public static string GoToReferenceNewTabCommand { + get { + return ResourceManager.GetString("GoToReferenceNewTabCommand", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to hides. + /// + public static string HidesParent { + get { + return ResourceManager.GetString("HidesParent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Implemented By. + /// + public static string ImplementedByTreeNode { + get { + return ResourceManager.GetString("ImplementedByTreeNode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Instantiated By. + /// + public static string InstantiatedByTreeNode { + get { + return ResourceManager.GetString("InstantiatedByTreeNode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Overridden By. + /// + public static string OverriddenByTreeNode { + get { + return ResourceManager.GetString("OverriddenByTreeNode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Overrides. + /// + public static string OverridesTreeNode { + get { + return ResourceManager.GetString("OverridesTreeNode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Analyzes assemblies. + /// + public static string Plugin_ShortDescription { + get { + return ResourceManager.GetString("Plugin_ShortDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Raised By. + /// + public static string RaisedByTreeNode { + get { + return ResourceManager.GetString("RaisedByTreeNode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Read By. + /// + public static string ReadByTreeNode { + get { + return ResourceManager.GetString("ReadByTreeNode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to _Remove. + /// + public static string RemoveCommand { + get { + return ResourceManager.GetString("RemoveCommand", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Searching.... + /// + public static string Searching { + get { + return ResourceManager.GetString("Searching", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shift+Dbl Click. + /// + public static string ShiftDoubleClick { + get { + return ResourceManager.GetString("ShiftDoubleClick", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Ctrl+C. + /// + public static string ShortCutKeyCtrlC { + get { + return ResourceManager.GetString("ShortCutKeyCtrlC", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Ctrl+Shift+R. + /// + public static string ShortCutKeyCtrlShiftR { + get { + return ResourceManager.GetString("ShortCutKeyCtrlShiftR", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Del. + /// + public static string ShortCutKeyDelete { + get { + return ResourceManager.GetString("ShortCutKeyDelete", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show Tokens. + /// + public static string ShowMetadataTokensCommand { + get { + return ResourceManager.GetString("ShowMetadataTokensCommand", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Single-click expands nodes. + /// + public static string SingleClickExpandNodes { + get { + return ResourceManager.GetString("SingleClickExpandNodes", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Syntax Highlight. + /// + public static string SyntaxHighlightCommand { + get { + return ResourceManager.GetString("SyntaxHighlightCommand", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Used By. + /// + public static string UsedByTreeNode { + get { + return ResourceManager.GetString("UsedByTreeNode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Uses. + /// + public static string UsesTreeNode { + get { + return ResourceManager.GetString("UsesTreeNode", resourceCulture); + } + } + } +} diff --git a/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.cs.resx b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.cs.resx new file mode 100644 index 0000000..1649a2d --- /dev/null +++ b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.cs.resx @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Analyzovat + + + Analyzér + + + Aplikováno na + + + Přiděleno + + + Kopírovat + + + Dvojklik + + + Vystavil + + + Rozšiřující metody + + + Přejít na odkaz + + + Ukázat odkaz v metodě + + + Ukázat odkaz v metodě (Nové okno) + + + Přejít na odkaz (Nové okno) + + + skrývá + + + Implementováno + + + Instanci vytvořil + + + Přepsal + + + Přepíše + + + Analyzuje sestavení + + + Zvednuto + + + Přečteno + + + Odstranit + + + Hledání... + + + Shift + dvojklepnutí + + + Ctrl + C + + + Ctrl+Shift+R + + + Del + + + Zobrazit tokeny + + + Jedno klepnutí rozbalí uzly + + + Zvýraznění syntaxe + + + Používáno + + + Používá + + \ No newline at end of file diff --git a/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.de.resx b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.de.resx new file mode 100644 index 0000000..754f7c5 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.de.resx @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Analy_sieren + + + Analysierer + + + Angewandt auf + + + Zugewiesen von + + + _Kopieren + + + Doppelklick + + + Sichtbar durch + + + Erweiterungsmethoden + + + Gehe zu Verweis + + + Verweis in Methode zeigen + + + Verweis in Methode zeigen (neuer _Tab) + + + Gehe zu Verweis (neuer Ta_b) + + + versteckt + + + Implementiert von + + + Instanziiert von + + + Überschrieben von + + + Überschreibt + + + Analysiert Assemblies + + + Ausgelöst durch + + + Gelesen von + + + _Entfernen + + + Suchen... + + + Umschalt+Doppelklick + + + Strg+C + + + Strg+Umschalt+R + + + Entf + + + Tokens anzeigen + + + Knoten durch einfachen Klick öffnen + + + Syntax hervorheben + + + Verwendet von + + + Benutzt + + \ No newline at end of file diff --git a/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.es-ES.resx b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.es-ES.resx new file mode 100644 index 0000000..dc78f2f --- /dev/null +++ b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.es-ES.resx @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Analizar Comando + + + Analizador + + + Aplicado a + + + Asignado por + + + Copi_a + + + Doble Click + + + Expuesto por + + + Métodos de extensión + + + Ir a la referencia + + + Ver Referencia en Método + + + Ver Referencia en Método (Nueva Pestaña) + + + Ir a referencia (Nueva Pestaña) + + + oculto + + + Implementado por + + + Instanciado por + + + Sobrescrito por + + + Sobrescrituras + + + Analiza ensamblados + + + Levantado por + + + Leído por + + + _Eliminar + + + Buscando... + + + Mayu+Doble Click + + + Ctrl+C + + + Ctrl+Shift+R + + + Eliminar + + + Mostrar Tokens + + + Expandir nodos con un solo click + + + Resaltar sintaxis + + + Utilizado por + + + Usos + + \ No newline at end of file diff --git a/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.fa.resx b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.fa.resx new file mode 100644 index 0000000..17ce23c --- /dev/null +++ b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.fa.resx @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + آنالیز + + + تحلیل گر + + + اعمال به + + + تخصیص یافته توسط + + + کپی + + + دابل کلیک + + + در معرض + + + توابع اضافی + + + برو به مرجع + + + نمایش مرجع در تابع + + + نمایش مرجع در تابع (سربرگ_جدید) + + + برو به مرجع (سربرگ_جدید) + + + پنهان کردن + + + پیاده سازی شده توسط + + + کشف شده توسط + + + رونویسی شده توسط + + + رونویس ها + + + تجزیه و تحلیل assemblis + + + شروع شده توسط + + + خوانده شده توسط + + + حذف + + + در حال جستجو... + + + Shit + Double Click + + + Ctrl+C + + + Ctrl+Shift+R + + + Del + + + نمایش توکن ها(Tokens) + + + تک کلیک برای گسترش گره ها + + + قالب برجسته + + + استفاده شده توسط + + + استفاده شده + + \ No newline at end of file diff --git a/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.fr.resx b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.fr.resx new file mode 100644 index 0000000..ac02539 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.fr.resx @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Analyser + + + Analyser + + + Appliqué à + + + Assigné par + + + Copier + + + Double clique + + + Exposé par + + + Méthodes d'extension + + + Aller à la référence + + + Voir la référence à la méthode + + + Montrer les références dans les méthodes(Nouvelle page) + + + Aller à la référence(Nouvelle page) + + + masqué + + + Mis en œuvre par + + + Instancié par + + + Substitué par + + + Surcharge + + + Analyser les Assembly + + + Déclenché par + + + Lu par + + + _Supprimer + + + Recherche en cours... + + + Shit + Double clique + + + Ctrl + C + + + Ctrl + Maj + R + + + Effacer + + + Montrer les tokens + + + Un clic simple ouvre les noeuds + + + Coloration syntaxique + + + Utilisé par + + + Utilise + + \ No newline at end of file diff --git a/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.hu.resx b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.hu.resx new file mode 100644 index 0000000..082ee8d --- /dev/null +++ b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.hu.resx @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + _Analizál + + + Analizáló / Elemző + + + Alkalmazva: + + + Hozzárendelve: + + + _Másolás + + + Dupla kattintás + + + kiajánlja + + + Kiterjesztés metódusok + + + Ugrás a referenciához + + + Referencia mutatása a metódusban + + + Referencia megjelenítése a metódusban (Új _lap) + + + Ugrás a referenciára (új _lap) + + + elrejti + + + implementál + + + példányosít + + + felülbírál + + + Felülbírál + + + Szerelvények analizálása + + + kiváltó + + + olvasva + + + _Eltávolít + + + Keresés... + + + Shift+dupla katt + + + Ctrl + C + + + Ctrl + Shift + R + + + Del + + + Token-ek mutatása + + + Egy kattintás kinyitja a csomópontot + + + Szintaxis kiemelése + + + Felhasznált + + + Használja + + diff --git a/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.it.resx b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.it.resx new file mode 100644 index 0000000..0a565b0 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.it.resx @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + _Analizza + + + Analizzatore + + + Applicato a + + + Assegnato da + + + _Copia + + + Doppio Click + + + Esposto da + + + Metodi di estensione + + + Vai al riferimento + + + Visualizza riferimento nel metodo + + + Visualizza riferimento nel metodo (Nuovo Tab) + + + Vai al riferimento (Nuovo Tab) + + + nascondi + + + Implementato da + + + Instanziato da + + + Ereditato da + + + Sostituzioni + + + Analizza gli assembly + + + Generato da + + + Letto da + + + _Rimuovi + + + Cerco... + + + Shift+Dbl Click + + + Ctrl+C + + + Ctrl+Shift+R + + + Canc + + + Mostra Tokens + + + Il singolo click espande i nodi + + + Evidenzia la sintassi + + + Utilizzato da + + + Utilizza + + \ No newline at end of file diff --git a/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.pt-BR.resx b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.pt-BR.resx new file mode 100644 index 0000000..84dfa77 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.pt-BR.resx @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Analisar + + + Analisador + + + Aplicado a + + + Atribuído por + + + Copiar + + + Duplo Clique + + + Exposto por + + + Métodos de extensão + + + Ir para Referência + + + Mostrar referência no Método + + + Mostrar Referência no Metodo (Nova _Guia) + + + Ir para Referência (Nova Guia) + + + ocultar + + + Implementado por + + + Instanciado por + + + Sobrescrito por + + + Sobrescrever + + + Analisa assemblies + + + Causado por + + + Lido por + + + _Remover + + + Pesquisando... + + + Shift + Clique Duplo + + + Ctrl+C + + + Ctrl+Shift+R + + + Del + + + Mostrar Tokens + + + Clique único expande nós + + + Realçar Sintaxe + + + Usado por + + + Usa + + \ No newline at end of file diff --git a/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.pt-PT.resx b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.pt-PT.resx new file mode 100644 index 0000000..ce31afc --- /dev/null +++ b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.pt-PT.resx @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Analisar + + + Analisador + + + Aplicado a + + + Atribuído Por + + + Copiar + + + Duplo Clique + + + Exposto Por + + + Métodos de Extensão + + + Ir para Referência + + + Mostrar referência em método + + + Mostrar Referência em Método (Novo Guia) + + + Ir para Referência (Novo Guia) + + + hides + + + Implementado Por + + + Instanciado Por + + + Overridden Por + + + Overrides + + + Analisa os assemblies + + + Raised Por + + + Lido por + + + Remover + + + Procurando... + + + Shift + Duplo Clique + + + Ctrl+C + + + Ctrl+Shift+R + + + Del + + + Mostrar Tokens + + + Clique único expande nós + + + Realce de sintaxe + + + Utilizado Por + + + Utiliza + + \ No newline at end of file diff --git a/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.resx b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.resx new file mode 100644 index 0000000..b621766 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.resx @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Analy_ze + + + Analyzer + + + Applied To + + + Assigned By + + + Cop_y + + + Dbl Click + + + Exposed By + + + Extension Methods + + + Go to Reference + + + Show Reference in Method + + + Show Reference in Method (New _Tab) + + + Go to Reference (New Ta_b) + + + hides + + + Implemented By + + + Instantiated By + + + Overridden By + + + Overrides + + + Analyzes assemblies + + + Raised By + + + Read By + + + _Remove + + + Searching... + + + Shift+Dbl Click + + + Ctrl+C + + + Ctrl+Shift+R + + + Del + + + Show Tokens + + + Single-click expands nodes + + + Syntax Highlight + + + Used By + + + Uses + + \ No newline at end of file diff --git a/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.ru.resx b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.ru.resx new file mode 100644 index 0000000..1dd2728 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.ru.resx @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Анали_зировать + + + Анализатор + + + Применяется к + + + Назначается в + + + Коп_ировать + + + Двойнок клик + + + Представлено в + + + Методы-расширения + + + Перейти по ссылке + + + Показать ссылку на метод + + + Показать ссылку в методе (новая _вкладка) + + + Перейти к ссылке (Новая вкла_дка) + + + hides + + + Реализовано в + + + Вызвано в + + + Перегружено в + + + Переопределения + + + Анализирует сборки + + + Вызывается в + + + Читается в + + + _Убрать + + + Поиск... + + + Shift+Dbl Click + + + Ctrl+C + + + Ctrl+Shift+R + + + Del + + + Показать токены + + + Разворачивать узлы одиночным щелчком + + + Подсветка синтаксиса + + + Используется в + + + Использует + + diff --git a/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.tr.resx b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.tr.resx new file mode 100644 index 0000000..edfc5c5 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.tr.resx @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Anali_z + + + Çözümleyici + + + Uygulanan + + + Atayan + + + Kop_yala + + + Çift tıklayın + + + Etkilendiren + + + Genişletme yöntemleri + + + Referansı izle + + + Metod içerisinde Referansı Göster + + + Method içerisinde Referansı Göster(Yeni _Sekme) + + + Referansa Git(Yeni _Sekme) + + + gizle + + + Uyarlayan + + + Tarafından örneği + + + Üzerine Yazan + + + Geçersiz Kıl + + + Derlemeleri analiz et + + + Tarafından yetiştirildi + + + Tarafından okundu + + + _Kaldır + + + Aranıyor... + + + Shift+Çift Tıklama + + + Ctrl + C + + + Ctrl+Shift+R + + + Del + + + Token'ları Göster + + + Tek tıklama düğümleri genişletir + + + Sözdizimi Renklendirme + + + Tarafından kullanılan + + + Kullanma + + \ No newline at end of file diff --git a/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.uk.resx b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.uk.resx new file mode 100644 index 0000000..1ff8a16 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.uk.resx @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Аналі_зувати + + + Аналізатор + + + Застосовано до + + + Ким Призначено + + + Копіюв_ати + + + Подвійний клік + + + Представлено в + + + Методи-розширення + + + Перейти до посилання + + + Показати посилання на методі + + + Показати посилання в методі (Нова _Вкладка) + + + Перейти до посилання (Нова Вкла_дка) + + + ховає + + + Реалізований + + + Ким створено примірник + + + Ким перевантажено + + + Перекриває + + + Аналізує збірки + + + Ким викликано + + + Ким читається + + + _Видалити + + + Пошук... + + + Shift+Подвійний клік + + + Ctrl+C + + + Ctrl+Shift+R + + + Del + + + Показати токени + + + Одноразове натискання розгортає вузли + + + Підсвітка синтаксису + + + Використовується + + + Використовує + + diff --git a/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.zh-CN.resx b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.zh-CN.resx new file mode 100644 index 0000000..f65b555 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/Properties/dnSpy.Analyzer.Resources.zh-CN.resx @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 分析(_Z) + + + 分析器 + + + 已应用到 + + + 赋值于 + + + 复制(_Y) + + + 双击 + + + 被公开于 + + + 扩展方法 + + + 转到引用 + + + 显示方法内的引用 + + + 显示方法内的引用(新标签页)(_T) + + + 转到引用(新标签页)(_B) + + + 隐藏 + + + 实现于 + + + 实例化于 + + + 重写于 + + + 重写 + + + 分析程序集 + + + 被触发于 + + + 读取于 + + + 移除(_R) + + + 搜索中… + + + Shift+双击 + + + Ctrl+C + + + Ctrl+Shift+R + + + Del + + + 显示标记 + + + 单击展开节点 + + + 语法高亮 + + + 被使用 + + + 使用 + + \ No newline at end of file diff --git a/Extensions/dnSpy.Analyzer/TheExtension.cs b/Extensions/dnSpy.Analyzer/TheExtension.cs new file mode 100644 index 0000000..51c3b52 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TheExtension.cs @@ -0,0 +1,38 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System.Collections.Generic; +using dnSpy.Analyzer.Properties; +using dnSpy.Contracts.Extension; + +namespace dnSpy.Analyzer { + [ExportExtension] + sealed class TheExtension : IExtension { + public IEnumerable MergedResourceDictionaries { + get { yield break; } + } + + public ExtensionInfo ExtensionInfo => new ExtensionInfo { + ShortDescription = dnSpy_Analyzer_Resources.Plugin_ShortDescription, + }; + + public void OnEvent(ExtensionEvent @event, object? obj) { + } + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/AnalyzerTreeNodeData.cs b/Extensions/dnSpy.Analyzer/TreeNodes/AnalyzerTreeNodeData.cs new file mode 100644 index 0000000..30303f9 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/AnalyzerTreeNodeData.cs @@ -0,0 +1,145 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using dnlib.DotNet; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Documents; +using dnSpy.Contracts.Images; +using dnSpy.Contracts.Text; +using dnSpy.Contracts.Text.Classification; +using dnSpy.Contracts.TreeView; +using dnSpy.Contracts.TreeView.Text; + +namespace dnSpy.Analyzer.TreeNodes { + abstract class AnalyzerTreeNodeData : TreeNodeData { + public override Guid Guid => Guid.Empty; + public sealed override bool SingleClickExpandsChildren => Context.SingleClickExpandsChildren; +#pragma warning disable CS8618 // Non-nullable field is uninitialized. + public IAnalyzerTreeNodeDataContext Context { get; set; } +#pragma warning restore CS8618 // Non-nullable field is uninitialized. + protected abstract ImageReference GetIcon(IDotNetImageService dnImgMgr); + protected virtual ImageReference? GetExpandedIcon(IDotNetImageService dnImgMgr) => null; + public sealed override ImageReference Icon => GetIcon(Context.DotNetImageService); + public sealed override ImageReference? ExpandedIcon => GetExpandedIcon(Context.DotNetImageService); + + static class Cache { + static readonly TextClassifierTextColorWriter writer = new TextClassifierTextColorWriter(); + public static TextClassifierTextColorWriter GetWriter() => writer; + public static void FreeWriter(TextClassifierTextColorWriter writer) => writer.Clear(); + } + + public sealed override object? Text { + get { + if (cachedText?.Target is object cached) + return cached; + + var writer = Cache.GetWriter(); + try { + Write(writer, Context.Decompiler); + var classifierContext = new TreeViewNodeClassifierContext(writer.Text, Context.TreeView, this, isToolTip: false, colorize: Context.SyntaxHighlight, colors: writer.Colors); + var elem = Context.TreeViewNodeTextElementProvider.CreateTextElement(classifierContext, TreeViewContentTypes.TreeViewNodeAnalyzer, TextElementFlags.FilterOutNewLines); + cachedText = new WeakReference(elem); + return elem; + } + finally { + Cache.FreeWriter(writer); + } + } + } + WeakReference? cachedText; + + protected abstract void Write(ITextColorWriter output, IDecompiler decompiler); + public sealed override object? ToolTip => null; + public sealed override string ToString() => ToString(Context.Decompiler); + + public string ToString(IDecompiler decompiler) { + var output = new StringBuilderTextColorOutput(); + Write(output, decompiler); + return output.ToString(); + } + + public sealed override void OnRefreshUI() => cachedText = null; + public abstract bool HandleAssemblyListChanged(IDsDocument[] removedAssemblies, IDsDocument[] addedAssemblies); + public abstract bool HandleModelUpdated(IDsDocument[] documents); + + public static void CancelSelfAndChildren(TreeNodeData node) { + foreach (var c in node.DescendantsAndSelf()) { + if (c is IAsyncCancellable id) + id.Cancel(); + } + } + + public static void HandleAssemblyListChanged(ITreeNode node, IDsDocument[] removedAssemblies, IDsDocument[] addedAssemblies) { + var children = node.DataChildren.ToArray(); + for (int i = children.Length - 1; i >= 0; i--) { + var c = children[i]; + var n = c as AnalyzerTreeNodeData; + if (n is null || !n.HandleAssemblyListChanged(removedAssemblies, addedAssemblies)) { + AnalyzerTreeNodeData.CancelSelfAndChildren(c); + node.Children.RemoveAt(i); + } + } + } + + public static void HandleModelUpdated(ITreeNode node, IDsDocument[] documents) { + var children = node.DataChildren.ToArray(); + for (int i = children.Length - 1; i >= 0; i--) { + var c = children[i]; + var n = c as AnalyzerTreeNodeData; + if (n is null || !n.HandleModelUpdated(documents)) { + AnalyzerTreeNodeData.CancelSelfAndChildren(c); + node.Children.RemoveAt(i); + } + } + } + + protected IMemberRef GetOriginalCodeLocation(IMemberRef member) { + // Emulate the original code. Only the C# override returned something other than the input + if (Context.Decompiler.UniqueGuid != DecompilerConstants.LANGUAGE_CSHARP_ILSPY) + return member; + if (!Context.Decompiler.Settings.GetBoolean(DecompilerOptionConstants.AnonymousMethods_GUID)) + return member; + return Helpers.GetOriginalCodeLocation(member); + } + + sealed class TheTreeNodeGroup : ITreeNodeGroup { + public static ITreeNodeGroup Instance = new TheTreeNodeGroup(); + + TheTreeNodeGroup() { + } + + public double Order => 100; + + public int Compare([AllowNull] TreeNodeData x, [AllowNull] TreeNodeData y) { + if (x == y) + return 0; + var a = x as AnalyzerTreeNodeData; + var b = y as AnalyzerTreeNodeData; + if (a is null) return -1; + if (b is null) return 1; + return StringComparer.OrdinalIgnoreCase.Compare(a.ToString(), b.ToString()); + } + } + + public override ITreeNodeGroup? TreeNodeGroup => TheTreeNodeGroup.Instance; + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/AssemblyNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/AssemblyNode.cs new file mode 100644 index 0000000..07518bd --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/AssemblyNode.cs @@ -0,0 +1,35 @@ +// 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 dnlib.DotNet; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Images; +using dnSpy.Contracts.Text; + +namespace dnSpy.Analyzer.TreeNodes { + sealed class AssemblyNode : EntityNode { + readonly AssemblyDef analyzedAssembly; + + public AssemblyNode(AssemblyDef analyzedAssembly) => this.analyzedAssembly = analyzedAssembly; + + protected override ImageReference GetIcon(IDotNetImageService dnImgMgr) => dnImgMgr.GetImageReference(analyzedAssembly); + protected override void Write(ITextColorWriter output, IDecompiler decompiler) => output.Write(analyzedAssembly); + public override IMemberRef? Member => null; + public override IMDTokenProvider? Reference => analyzedAssembly; + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/AsyncFetchChildrenHelper.cs b/Extensions/dnSpy.Analyzer/TreeNodes/AsyncFetchChildrenHelper.cs new file mode 100644 index 0000000..728646a --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/AsyncFetchChildrenHelper.cs @@ -0,0 +1,97 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; +using System.Diagnostics.CodeAnalysis; +using dnSpy.Analyzer.Properties; +using dnSpy.Contracts.Images; +using dnSpy.Contracts.Text; +using dnSpy.Contracts.Text.Classification; +using dnSpy.Contracts.TreeView; +using dnSpy.Contracts.TreeView.Text; + +namespace dnSpy.Analyzer.TreeNodes { + sealed class AsyncFetchChildrenHelper : AsyncNodeProvider { + readonly SearchNode node; + readonly Action completed; + + public AsyncFetchChildrenHelper(SearchNode node, Action completed) + : base(node) { + this.node = node; + this.completed = completed; + Start(); + } + + sealed class MessageNodeTreeNodeGroup : ITreeNodeGroup { + public double Order { get; } + + public int Compare([AllowNull] TreeNodeData x, [AllowNull] TreeNodeData y) { + if (x == y) + return 0; + var a = x as MessageNode; + var b = y as MessageNode; + if (a is null) return -1; + if (b is null) return 1; + return 0; + } + } + + sealed class MessageNode : TreeNodeData { + public override Guid Guid => Guid.Empty; + public override ImageReference Icon => DsImages.Search; + public override object? ToolTip => null; + public override void OnRefreshUI() { } + public override ITreeNodeGroup? TreeNodeGroup => treeNodeGroup; + readonly ITreeNodeGroup treeNodeGroup = new MessageNodeTreeNodeGroup(); + + readonly IAnalyzerTreeNodeDataContext context; + + public MessageNode(IAnalyzerTreeNodeDataContext context) => this.context = context; + + static class Cache { + static readonly TextClassifierTextColorWriter writer = new TextClassifierTextColorWriter(); + public static TextClassifierTextColorWriter GetWriter() => writer; + public static void FreeWriter(TextClassifierTextColorWriter writer) => writer.Clear(); + } + + public override object? Text { + get { + var writer = Cache.GetWriter(); + try { + writer.Write(BoxedTextColor.Text, dnSpy_Analyzer_Resources.Searching); + var classifierContext = new TreeViewNodeClassifierContext(writer.Text, context.TreeView, this, isToolTip: false, colorize: context.SyntaxHighlight, colors: writer.Colors); + var elem = context.TreeViewNodeTextElementProvider.CreateTextElement(classifierContext, TreeViewContentTypes.TreeViewNodeAnalyzer, TextElementFlags.FilterOutNewLines); + return elem; + } + finally { + Cache.FreeWriter(writer); + } + } + } + } + + protected override void ThreadMethod() { + AddMessageNode(() => new MessageNode(node.Context)); + foreach (var child in node.FetchChildrenInternal(cancellationToken)) + AddNode(child); + } + + protected override void OnCompleted() => completed(); + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/AttributeAppliedToNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/AttributeAppliedToNode.cs new file mode 100644 index 0000000..6023c20 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/AttributeAppliedToNode.cs @@ -0,0 +1,310 @@ +// 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 System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using dnlib.DotNet; +using dnSpy.Analyzer.Properties; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Text; + +namespace dnSpy.Analyzer.TreeNodes { + sealed class AttributeAppliedToNode : SearchNode { + readonly TypeDef analyzedType; + readonly List analyzedTypes; + readonly bool includeAllModules; + + readonly AttributeTargets usage; + ConcurrentDictionary? foundMethods; + + public static bool CanShow(TypeDef type) => type.IsClass && IsCustomAttribute(type); + + static bool IsCustomAttribute(TypeDef type) { + while (type is not null) { + var bt = type.BaseType.ResolveTypeDef(); + if (bt is null) + return false; + if (bt.FullName == "System.Attribute") + return true; + type = bt; + } + return false; + } + + public AttributeAppliedToNode(TypeDef analyzedType) { + this.analyzedType = analyzedType ?? throw new ArgumentNullException(nameof(analyzedType)); + analyzedTypes = new List { analyzedType }; + includeAllModules = CustomAttributesUtils.IsPseudoCustomAttributeType(analyzedType); + var ca = analyzedType.CustomAttributes.Find("System.AttributeUsageAttribute"); + if (ca is not null && ca.ConstructorArguments.Count == 1 && ca.ConstructorArguments[0].Value is int) + usage = (AttributeTargets)ca.ConstructorArguments[0].Value; + else + usage = AttributeTargets.All; + } + + protected override void Write(ITextColorWriter output, IDecompiler decompiler) => + output.Write(BoxedTextColor.Text, dnSpy_Analyzer_Resources.AppliedToTreeNode); + + protected override IEnumerable FetchChildren(CancellationToken ct) { + foundMethods = new ConcurrentDictionary(); + + AddTypeEquivalentTypes(Context.DocumentService, analyzedType, analyzedTypes); + IEnumerable<(ModuleDef module, ITypeDefOrRef type)> modules; + if (includeAllModules) + modules = GetAllModules(analyzedType.Module, ct); + else if (TIAHelper.IsTypeDefEquivalent(analyzedType)) + modules = GetTypeEquivalentModulesAndTypes(analyzedTypes); + else if (IsPublic(analyzedType)) + modules = GetReferencingModules(analyzedType.Module, ct); + else + modules = GetModuleAndAnyFriends(analyzedType.Module, ct); + + var results = modules.AsParallel().WithCancellation(ct).SelectMany(a => FindReferencesInModule(new[] { a.Item1 }, a.Item2, ct)); + + foreach (var result in results) + yield return result; + + foundMethods = null; + } + + static bool IsPublic(TypeDef type) { + for (;;) { + if (type.DeclaringType is TypeDef declType) { + if (!(type.IsNestedFamily || type.IsNestedFamilyOrAssembly || type.IsNestedPublic)) + return false; + type = declType; + } + else + return type.IsPublic; + } + } + + IEnumerable FindReferencesInModule(IEnumerable modules, ITypeDefOrRef tr, CancellationToken ct) { + var trScopeType = tr.GetScopeType(); + var checkedAsms = new HashSet(); + foreach (var module in modules) { + if ((usage & AttributeTargets.Assembly) != 0) { + AssemblyDef asm = module.Assembly; + if (asm is not null && checkedAsms.Add(asm)) { + foreach (var attribute in asm.GetCustomAttributes()) { + if (new SigComparer().Equals(attribute.AttributeType?.GetScopeType(), trScopeType)) { + yield return new AssemblyNode(asm) { Context = Context }; + break; + } + } + } + } + + ct.ThrowIfCancellationRequested(); + + if ((usage & AttributeTargets.Module) != 0) { + foreach (var attribute in module.GetCustomAttributes()) { + if (new SigComparer().Equals(attribute.AttributeType?.GetScopeType(), trScopeType)) { + yield return new ModuleNode(module) { Context = Context }; + break; + } + } + } + + ct.ThrowIfCancellationRequested(); + + if ((usage & (AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.GenericParameter | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Event | AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.ReturnValue | AttributeTargets.Parameter)) != 0) { + foreach (TypeDef type in TreeTraversal.PreOrder(module.Types, t => t.NestedTypes)) { + ct.ThrowIfCancellationRequested(); + foreach (var result in FindReferencesWithinInType(type, tr)) { + ct.ThrowIfCancellationRequested(); + yield return result; + } + } + } + } + } + + IEnumerable FindReferencesWithinInType(TypeDef type, ITypeDefOrRef attrTypeRef) { + var attrTypeRefScopeType = attrTypeRef.GetScopeType(); + bool searchRequired = (type.IsClass && usage.HasFlag(AttributeTargets.Class)) + || (type.IsEnum && usage.HasFlag(AttributeTargets.Enum)) + || (type.IsInterface && usage.HasFlag(AttributeTargets.Interface)) + || (type.IsValueType && usage.HasFlag(AttributeTargets.Struct)); + if (searchRequired) { + foreach (var attribute in type.GetCustomAttributes()) { + if (new SigComparer().Equals(attribute.AttributeType?.GetScopeType(), attrTypeRefScopeType)) { + yield return new TypeNode(type) { Context = Context }; + break; + } + } + } + + if ((usage & AttributeTargets.GenericParameter) != 0 && type.HasGenericParameters) { + foreach (var parameter in type.GenericParameters) { + foreach (var attribute in parameter.GetCustomAttributes()) { + if (new SigComparer().Equals(attribute.AttributeType?.GetScopeType(), attrTypeRefScopeType)) { + yield return new TypeNode(type) { Context = Context }; + break; + } + } + } + } + + if ((usage & AttributeTargets.Field) != 0 && type.HasFields) { + foreach (var field in type.Fields) { + foreach (var attribute in field.GetCustomAttributes()) { + if (new SigComparer().Equals(attribute.AttributeType?.GetScopeType(), attrTypeRefScopeType)) { + yield return new FieldNode(field) { Context = Context }; + break; + } + } + } + } + + if (((usage & AttributeTargets.Property) != 0) && type.HasProperties) { + foreach (var property in type.Properties) { + foreach (var attribute in property.GetCustomAttributes()) { + if (new SigComparer().Equals(attribute.AttributeType?.GetScopeType(), attrTypeRefScopeType)) { + yield return new PropertyNode(property) { Context = Context }; + break; + } + } + } + } + if (((usage & AttributeTargets.Event) != 0) && type.HasEvents) { + foreach (var @event in type.Events) { + foreach (var attribute in @event.GetCustomAttributes()) { + if (new SigComparer().Equals(attribute.AttributeType?.GetScopeType(), attrTypeRefScopeType)) { + yield return new EventNode(@event) { Context = Context }; + break; + } + } + } + } + + if ((usage & (AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.ReturnValue | AttributeTargets.Parameter)) != 0 && type.HasMethods) { + foreach (var method in type.Methods) { + bool found = false; + if ((usage & (AttributeTargets.Method | AttributeTargets.Constructor)) != 0) { + foreach (var attribute in method.GetCustomAttributes()) { + if (new SigComparer().Equals(attribute.AttributeType?.GetScopeType(), attrTypeRefScopeType)) { + found = true; + break; + } + } + } + if (!found && + ((usage & AttributeTargets.ReturnValue) != 0) && + method.Parameters.ReturnParameter.ParamDef is ParamDef retParamDef) { + foreach (var attribute in retParamDef.GetCustomAttributes()) { + if (new SigComparer().Equals(attribute.AttributeType?.GetScopeType(), attrTypeRefScopeType)) { + found = true; + break; + } + } + } + + if (!found && + ((usage & AttributeTargets.Parameter) != 0) && + method.Parameters.Count > 0) { + foreach (var parameter in method.Parameters.Where(param => param.HasParamDef)) { + if (parameter.IsHiddenThisParameter) + continue; + foreach (var attribute in parameter.ParamDef.GetCustomAttributes()) { + if (new SigComparer().Equals(attribute.AttributeType?.GetScopeType(), attrTypeRefScopeType)) { + found = true; + break; + } + } + } + } + + if (found) { + if (GetOriginalCodeLocation(method) is MethodDef codeLocation && !HasAlreadyBeenFound(codeLocation)) { + yield return new MethodNode(codeLocation) { Context = Context }; + } + } + } + } + } + + bool HasAlreadyBeenFound(MethodDef method) => !foundMethods!.TryAdd(method, 0); + + IEnumerable<(ModuleDef module, ITypeDefOrRef type)> GetAllModules(ModuleDef mod, CancellationToken ct) { + foreach (var doc in Context.DocumentService.GetDocuments()) { + if (!(doc.ModuleDef is ModuleDef module)) + continue; + var typeRef = GetScopeTypeRefInModule(module) ?? module.Import(analyzedType); + yield return (module, typeRef); + } + } + + IEnumerable<(ModuleDef module, ITypeDefOrRef type)> GetReferencingModules(ModuleDef mod, CancellationToken ct) { + var asm = mod.Assembly; + if (asm is null) { + yield return (mod, analyzedType); + yield break; + } + + foreach (var m in asm.Modules) + yield return (m, analyzedType); + + var modules = Context.DocumentService.GetDocuments().Where(a => SearchNode.CanIncludeModule(mod, a.ModuleDef)); + + foreach (var module in modules) { + Debug2.Assert(module.ModuleDef is not null); + ct.ThrowIfCancellationRequested(); + var typeref = GetScopeTypeRefInModule(module.ModuleDef); + if (typeref is not null) + yield return (module.ModuleDef, typeref); + } + } + + IEnumerable<(ModuleDef module, ITypeDefOrRef type)> GetModuleAndAnyFriends(ModuleDef mod, CancellationToken ct) { + var asm = mod.Assembly; + if (asm is null) { + yield return (mod, analyzedType); + yield break; + } + + foreach (var m in asm.Modules) + yield return (m, analyzedType); + + var friendAssemblies = GetFriendAssemblies(Context.DocumentService, mod, out var modules); + if (friendAssemblies.Count > 0) { + foreach (var module in modules) { + Debug2.Assert(module.ModuleDef is not null); + ct.ThrowIfCancellationRequested(); + if (module.AssemblyDef is null || friendAssemblies.Contains(module.AssemblyDef.Name)) { + var typeref = GetScopeTypeRefInModule(module.ModuleDef); + if (typeref is not null) + yield return (module.ModuleDef, typeref); + } + } + } + } + + ITypeDefOrRef? GetScopeTypeRefInModule(ModuleDef mod) { + foreach (var typeref in mod.GetTypeRefs()) { + if (new SigComparer().Equals(analyzedType, typeref)) + return typeref; + } + return null; + } + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/ComUtils.cs b/Extensions/dnSpy.Analyzer/TreeNodes/ComUtils.cs new file mode 100644 index 0000000..1457e71 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/ComUtils.cs @@ -0,0 +1,143 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; +using dnlib.DotNet; + +namespace dnSpy.Analyzer.TreeNodes { + static class ComUtils { + public static bool IsComType(TypeDef type, out Guid guid) { + guid = default; + + // type.IsImport (== ComImportAttribute) isn't checked since the CLR doesn't require + // it to be set. If it has a GuidAttribute, it's probably a COM type. + + if (!type.IsInterface) + return false; + + var ca = type.CustomAttributes.Find("System.Runtime.InteropServices.GuidAttribute"); + if (ca is null || ca.ConstructorArguments.Count != 1) + return false; + if (!(ca.ConstructorArguments[0].Value is UTF8String guidStr) || !Guid.TryParse(guidStr, out guid)) + return false; + + return true; + } + + public static bool ComEquals(TypeDef type, ref Guid guid) { + // type.IsImport (== ComImportAttribute) isn't checked since the CLR doesn't require + // it to be set. If it has a GuidAttribute, it's probably a COM type. + + if (!type.IsInterface) + return false; + + var ca = type.CustomAttributes.Find("System.Runtime.InteropServices.GuidAttribute"); + if (ca is null || ca.ConstructorArguments.Count != 1) + return false; + if (!(ca.ConstructorArguments[0].Value is UTF8String guidStr) || !Guid.TryParse(guidStr, out var g)) + return false; + + return g == guid; + } + + static bool IsVtblGap(MethodDef method) => IsVtblGap(method, out _); + + public static bool IsVtblGap(MethodDef method, out int count) { + count = 0; + if (!(method.IsVirtual || method.IsAbstract)) + return false; + if (!method.IsRuntimeSpecialName) + return false; + string name = method.Name; + const string vtblGapPrefix = "_VtblGap"; + if (name is null || !name.StartsWith(vtblGapPrefix)) + return false; + int i = vtblGapPrefix.Length; + while (i < name.Length) { + var c = name[i]; + if (!('0' <= c && c <= '9')) + break; + i++; + } + if (i == name.Length) + count = 1; + else { + if (name[i++] != '_') + return false; + if (i == name.Length) + return false; + while (i < name.Length) { + var c = name[i]; + if (!('0' <= c && c <= '9')) + break; + count = (ushort)((uint)count * 10 + c - '0'); + i++; + } + if (i != name.Length) + return false; + } + return true; + } + + static int GetVtblIndex(MethodDef method) { + int vtblIndex = 0; + var methods = method.DeclaringType.Methods; + for (int i = 0; i < methods.Count; i++) { + var m = methods[i]; + if (!(m.IsVirtual || m.IsAbstract)) + continue; + if (m == method) + return vtblIndex; + if (IsVtblGap(m, out var count)) + vtblIndex += count; + else + vtblIndex++; + } + return -1; + } + + public static void GetMemberInfo(MethodDef method, out bool isComType, out Guid comGuid, out int vtblIndex) { + comGuid = default; + isComType = (method.IsVirtual || method.IsAbstract) && !IsVtblGap(method) && IsComType(method.DeclaringType, out comGuid); + if (isComType) + vtblIndex = GetVtblIndex(method); + else + vtblIndex = -1; + } + + public static MethodDef? GetMethod(TypeDef type, int vtblIndex) { + int currentVtblIndex = 0; + var methods = type.Methods; + for (int i = 0; i < methods.Count; i++) { + var method = methods[i]; + if (!(method.IsVirtual || method.IsAbstract)) + continue; + if (!IsVtblGap(method, out int count)) { + if (currentVtblIndex == vtblIndex) + return method; + count = 1; + } + currentVtblIndex += count; + if (currentVtblIndex > vtblIndex) + break; + } + return null; + } + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/Commands.cs b/Extensions/dnSpy.Analyzer/TreeNodes/Commands.cs new file mode 100644 index 0000000..0b7dce0 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/Commands.cs @@ -0,0 +1,326 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; +using System.Windows.Controls; +using System.Windows.Input; +using dnlib.DotNet; +using dnSpy.Contracts.Controls; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Documents.Tabs; +using dnSpy.Contracts.Documents.Tabs.DocViewer; +using dnSpy.Contracts.Extension; +using dnSpy.Contracts.Images; +using dnSpy.Contracts.Menus; +using dnSpy.Contracts.Search; +using dnSpy.Contracts.ToolWindows.App; +using dnSpy.Contracts.TreeView; + +namespace dnSpy.Analyzer.TreeNodes { + [ExportAutoLoaded] + sealed class AnalyzeCommandLoader : IAutoLoaded { + public static readonly RoutedCommand AnalyzeRoutedCommand = new RoutedCommand("AnalyzeRoutedCommand", typeof(AnalyzeCommandLoader)); + readonly IDsToolWindowService toolWindowService; + readonly IDocumentTabService documentTabService; + readonly Lazy analyzerService; + readonly IDecompilerService decompilerService; + + [ImportingConstructor] + AnalyzeCommandLoader(IDsToolWindowService toolWindowService, IWpfCommandService wpfCommandService, IDocumentTabService documentTabService, Lazy analyzerService, IDecompilerService decompilerService) { + this.toolWindowService = toolWindowService; + this.documentTabService = documentTabService; + this.analyzerService = analyzerService; + this.decompilerService = decompilerService; + + var cmds = wpfCommandService.GetCommands(ControlConstants.GUID_DOCUMENTVIEWER_UICONTEXT); + cmds.Add(AnalyzeRoutedCommand, TextEditor_Executed, TextEditor_CanExecute, ModifierKeys.Control | ModifierKeys.Shift, Key.R); + cmds.Add(AnalyzeRoutedCommand, ShowAnalyzerExecuted, ShowAnalyzerCanExecute, ModifierKeys.Control | ModifierKeys.Shift, Key.R); + + cmds = wpfCommandService.GetCommands(ControlConstants.GUID_DOCUMENT_TREEVIEW); + cmds.Add(AnalyzeRoutedCommand, DocumentTreeView_Executed, DocumentTreeView_CanExecute, ModifierKeys.Control | ModifierKeys.Shift, Key.R); + cmds.Add(AnalyzeRoutedCommand, ShowAnalyzerExecuted, ShowAnalyzerCanExecute, ModifierKeys.Control | ModifierKeys.Shift, Key.R); + + cmds = wpfCommandService.GetCommands(ControlConstants.GUID_ANALYZER_TREEVIEW); + cmds.Add(AnalyzeRoutedCommand, AnalyzerTreeView_Executed, AnalyzerTreeView_CanExecute, ModifierKeys.Control | ModifierKeys.Shift, Key.R); + cmds.Add(AnalyzeRoutedCommand, ShowAnalyzerExecuted, ShowAnalyzerCanExecute, ModifierKeys.Control | ModifierKeys.Shift, Key.R); + + cmds = wpfCommandService.GetCommands(ControlConstants.GUID_SEARCH_LISTBOX); + cmds.Add(AnalyzeRoutedCommand, SearchListBox_Executed, SearchListBox_CanExecute, ModifierKeys.Control | ModifierKeys.Shift, Key.R); + cmds.Add(AnalyzeRoutedCommand, ShowAnalyzerExecuted, ShowAnalyzerCanExecute, ModifierKeys.Control | ModifierKeys.Shift, Key.R); + } + + void ShowAnalyzerCanExecute(object? sender, CanExecuteRoutedEventArgs e) => + e.CanExecute = toolWindowService.IsShown(AnalyzerToolWindowContent.THE_GUID); + void ShowAnalyzerExecuted(object? sender, ExecutedRoutedEventArgs e) => + toolWindowService.Show(AnalyzerToolWindowContent.THE_GUID); + void TextEditor_CanExecute(object? sender, CanExecuteRoutedEventArgs e) => + e.CanExecute = AnalyzeCommand.CanAnalyze(TextEditor_GetMemberRef(), decompilerService.Decompiler); + void TextEditor_Executed(object? sender, ExecutedRoutedEventArgs e) => + AnalyzeCommand.Analyze(toolWindowService, analyzerService, decompilerService.Decompiler, TextEditor_GetMemberRef()); + IMemberRef? TextEditor_GetMemberRef() => + (documentTabService.ActiveTab?.UIContext as IDocumentViewer)?.SelectedReference?.Data.Reference as IMemberRef; + void DocumentTreeView_CanExecute(object? sender, CanExecuteRoutedEventArgs e) => + e.CanExecute = AnalyzeCommand.CanAnalyze(DocumentTreeView_GetMemberRef(), decompilerService.Decompiler); + void DocumentTreeView_Executed(object? sender, ExecutedRoutedEventArgs e) => + AnalyzeCommand.Analyze(toolWindowService, analyzerService, decompilerService.Decompiler, DocumentTreeView_GetMemberRef()); + + IMemberRef? DocumentTreeView_GetMemberRef() { + var nodes = documentTabService.DocumentTreeView.TreeView.TopLevelSelection; + var node = nodes.Length == 0 ? null : nodes[0] as IMDTokenNode; + return node?.Reference as IMemberRef; + } + + void AnalyzerTreeView_CanExecute(object? sender, CanExecuteRoutedEventArgs e) => + e.CanExecute = AnalyzeCommand.CanAnalyze(AnalyzerTreeView_GetMemberRef(), decompilerService.Decompiler); + void AnalyzerTreeView_Executed(object? sender, ExecutedRoutedEventArgs e) => + AnalyzeCommand.Analyze(toolWindowService, analyzerService, decompilerService.Decompiler, AnalyzerTreeView_GetMemberRef()); + + IMemberRef? AnalyzerTreeView_GetMemberRef() { + var nodes = analyzerService.Value.TreeView.TopLevelSelection; + var node = nodes.Length == 0 ? null : nodes[0] as IMDTokenNode; + return node?.Reference as IMemberRef; + } + + void SearchListBox_CanExecute(object? sender, CanExecuteRoutedEventArgs e) => + e.CanExecute = AnalyzeCommand.CanAnalyze(SearchListBox_GetMemberRef(e.Source as ListBox), decompilerService.Decompiler); + void SearchListBox_Executed(object? sender, ExecutedRoutedEventArgs e) => + AnalyzeCommand.Analyze(toolWindowService, analyzerService, decompilerService.Decompiler, SearchListBox_GetMemberRef(e.Source as ListBox)); + IMemberRef? SearchListBox_GetMemberRef(ListBox? listBox) => + (listBox?.SelectedItem as ISearchResultReferenceProvider)?.Reference as IMemberRef; + } + + static class AnalyzeCommand { + [ExportMenuItem(Header = "res:AnalyzeCommand", Icon = DsImagesAttribute.Search, InputGestureText = "res:ShortCutKeyCtrlShiftR", Group = MenuConstants.GROUP_CTX_DOCUMENTS_OTHER, Order = 0)] + sealed class FilesCommand : MenuItemBase { + readonly IDsToolWindowService toolWindowService; + readonly IDecompilerService decompilerService; + readonly Lazy analyzerService; + + [ImportingConstructor] + FilesCommand(IDsToolWindowService toolWindowService, IDecompilerService decompilerService, Lazy analyzerService) { + this.toolWindowService = toolWindowService; + this.decompilerService = decompilerService; + this.analyzerService = analyzerService; + } + + public override bool IsVisible(IMenuItemContext context) => GetMemberRefs(context).Any(); + IEnumerable GetMemberRefs(IMenuItemContext context) => + GetMemberRefs(context, MenuConstants.GUIDOBJ_DOCUMENTS_TREEVIEW_GUID, false, decompilerService); + + internal static IEnumerable GetMemberRefs(IMenuItemContext context, string guid, bool checkRoot, IDecompilerService decompilerService) { + if (context.CreatorObject.Guid != new Guid(guid)) + yield break; + var nodes = context.Find(); + if (nodes is null) + yield break; + + if (checkRoot && nodes.All(a => a.TreeNode.Parent is not null && a.TreeNode.Parent.Parent is null)) + yield break; + + foreach (var node in nodes) { + if (node is IMDTokenNode mr && CanAnalyze(mr.Reference as IMemberRef, decompilerService.Decompiler)) + yield return mr.Reference as IMemberRef; + } + } + + public override void Execute(IMenuItemContext context) => + Analyze(toolWindowService, analyzerService, decompilerService.Decompiler, GetMemberRefs(context)); + } + + [ExportMenuItem(Header = "res:AnalyzeCommand", Icon = DsImagesAttribute.Search, InputGestureText = "res:ShortCutKeyCtrlShiftR", Group = MenuConstants.GROUP_CTX_ANALYZER_OTHER, Order = 0)] + sealed class AnalyzerCommand : MenuItemBase { + readonly IDsToolWindowService toolWindowService; + readonly IDecompilerService decompilerService; + readonly Lazy analyzerService; + + [ImportingConstructor] + AnalyzerCommand(IDsToolWindowService toolWindowService, IDecompilerService decompilerService, Lazy analyzerService) { + this.toolWindowService = toolWindowService; + this.decompilerService = decompilerService; + this.analyzerService = analyzerService; + } + + public override bool IsVisible(IMenuItemContext context) => GetMemberRefs(context).Any(); + IEnumerable GetMemberRefs(IMenuItemContext context) => + FilesCommand.GetMemberRefs(context, MenuConstants.GUIDOBJ_ANALYZER_TREEVIEW_GUID, true, decompilerService); + public override void Execute(IMenuItemContext context) => + Analyze(toolWindowService, analyzerService, decompilerService.Decompiler, GetMemberRefs(context)); + } + + [ExportMenuItem(Header = "res:AnalyzeCommand", Icon = DsImagesAttribute.Search, InputGestureText = "res:ShortCutKeyCtrlShiftR", Group = MenuConstants.GROUP_CTX_DOCVIEWER_OTHER, Order = 0)] + sealed class CodeCommand : MenuItemBase { + readonly IDsToolWindowService toolWindowService; + readonly IDecompilerService decompilerService; + readonly Lazy analyzerService; + + [ImportingConstructor] + CodeCommand(IDsToolWindowService toolWindowService, IDecompilerService decompilerService, Lazy analyzerService) { + this.toolWindowService = toolWindowService; + this.decompilerService = decompilerService; + this.analyzerService = analyzerService; + } + + public override bool IsVisible(IMenuItemContext context) => GetMemberRefs(context).Any(); + static IEnumerable GetMemberRefs(IMenuItemContext context) => + GetMemberRefs(context, MenuConstants.GUIDOBJ_DOCUMENTVIEWERCONTROL_GUID); + + internal static IEnumerable GetMemberRefs(IMenuItemContext context, string guid) { + if (context.CreatorObject.Guid != new Guid(guid)) + yield break; + + var @ref = context.Find(); + if (@ref is not null) { + if (@ref.Reference is IMemberRef mr) + yield return mr; + } + } + + public override void Execute(IMenuItemContext context) => + Analyze(toolWindowService, analyzerService, decompilerService.Decompiler, GetMemberRefs(context)); + } + + [ExportMenuItem(Header = "res:AnalyzeCommand", Icon = DsImagesAttribute.Search, InputGestureText = "res:ShortCutKeyCtrlShiftR", Group = MenuConstants.GROUP_CTX_SEARCH_OTHER, Order = 0)] + sealed class SearchCommand : MenuItemBase { + readonly IDsToolWindowService toolWindowService; + readonly IDecompilerService decompilerService; + readonly Lazy analyzerService; + + [ImportingConstructor] + SearchCommand(IDsToolWindowService toolWindowService, IDecompilerService decompilerService, Lazy analyzerService) { + this.toolWindowService = toolWindowService; + this.decompilerService = decompilerService; + this.analyzerService = analyzerService; + } + + static IEnumerable GetMemberRefs(IMenuItemContext context) => + CodeCommand.GetMemberRefs(context, MenuConstants.GUIDOBJ_SEARCH_GUID); + public override bool IsVisible(IMenuItemContext context) => GetMemberRefs(context).Any(); + public override void Execute(IMenuItemContext context) => + Analyze(toolWindowService, analyzerService, decompilerService.Decompiler, GetMemberRefs(context)); + } + + public static bool CanAnalyze(IMemberRef? member, IDecompiler decompiler) { + member = ResolveReference(member); + return member is TypeDef || + member is FieldDef || + member is MethodDef || + PropertyNode.CanShow(member, decompiler) || + EventNode.CanShow(member, decompiler); + } + + static void Analyze(IDsToolWindowService toolWindowService, Lazy analyzerService, IDecompiler decompiler, IEnumerable mrs) { + foreach (var mr in mrs) + Analyze(toolWindowService, analyzerService, decompiler, mr); + } + + public static void Analyze(IDsToolWindowService toolWindowService, Lazy analyzerService, IDecompiler decompiler, IMemberRef? member) { + var memberDef = ResolveReference(member); + + if (memberDef is TypeDef type) { + toolWindowService.Show(AnalyzerToolWindowContent.THE_GUID); + analyzerService.Value.Add(new TypeNode(type)); + } + + if (memberDef is FieldDef field) { + toolWindowService.Show(AnalyzerToolWindowContent.THE_GUID); + analyzerService.Value.Add(new FieldNode(field)); + } + + if (memberDef is MethodDef method) { + toolWindowService.Show(AnalyzerToolWindowContent.THE_GUID); + analyzerService.Value.Add(new MethodNode(method)); + } + + var propertyAnalyzer = PropertyNode.TryCreateAnalyzer(member, decompiler); + if (propertyAnalyzer is not null) { + toolWindowService.Show(AnalyzerToolWindowContent.THE_GUID); + analyzerService.Value.Add(propertyAnalyzer); + } + + var eventAnalyzer = EventNode.TryCreateAnalyzer(member, decompiler); + if (eventAnalyzer is not null) { + toolWindowService.Show(AnalyzerToolWindowContent.THE_GUID); + analyzerService.Value.Add(eventAnalyzer); + } + } + + static IMemberDef? ResolveReference(object? reference) { + if (reference is ITypeDefOrRef) + return ((ITypeDefOrRef)reference).ResolveTypeDef(); + else if (reference is IMethod && ((IMethod)reference).MethodSig is not null) + return ((IMethod)reference).ResolveMethodDef(); + else if (reference is IField) + return ((IField)reference).ResolveFieldDef(); + else if (reference is PropertyDef) + return (PropertyDef)reference; + else if (reference is EventDef) + return (EventDef)reference; + return null; + } + } + + [ExportAutoLoaded] + sealed class RemoveAnalyzeCommand : IAutoLoaded { + readonly Lazy analyzerService; + + [ImportingConstructor] + RemoveAnalyzeCommand(IWpfCommandService wpfCommandService, Lazy analyzerService) { + this.analyzerService = analyzerService; + var cmds = wpfCommandService.GetCommands(ControlConstants.GUID_ANALYZER_TREEVIEW); + cmds.Add(ApplicationCommands.Delete, (s, e) => DeleteNodes(), (s, e) => e.CanExecute = CanDeleteNodes, ModifierKeys.None, Key.Delete); + } + + bool CanDeleteNodes => GetNodes() is not null; + void DeleteNodes() => DeleteNodes(GetNodes()); + TreeNodeData[]? GetNodes() => GetNodes(analyzerService.Value.TreeView.TopLevelSelection); + + internal static TreeNodeData[]? GetNodes(TreeNodeData[] nodes) { + if (nodes is null) + return null; + if (nodes.Length == 0 || !nodes.All(a => a.TreeNode.Parent is not null && a.TreeNode.Parent.Parent is null)) + return null; + return nodes; + } + + internal static void DeleteNodes(TreeNodeData[]? nodes) { + if (nodes is not null) { + foreach (var node in nodes) { + AnalyzerTreeNodeData.CancelSelfAndChildren(node); + node.TreeNode.Parent!.Children.Remove(node.TreeNode); + } + } + } + } + + [ExportMenuItem(Header = "res:RemoveCommand", Icon = DsImagesAttribute.Cancel, InputGestureText = "res:ShortCutKeyDelete", Group = MenuConstants.GROUP_CTX_ANALYZER_OTHER, Order = 10)] + sealed class RemoveAnalyzeCtxMenuCommand : MenuItemBase { + public override bool IsVisible(IMenuItemContext context) => GetNodes(context) is not null; + + static TreeNodeData[]? GetNodes(IMenuItemContext context) { + if (context.CreatorObject.Guid != new Guid(MenuConstants.GUIDOBJ_ANALYZER_TREEVIEW_GUID)) + return null; + return RemoveAnalyzeCommand.GetNodes(context.Find()); + } + + public override void Execute(IMenuItemContext context) => RemoveAnalyzeCommand.DeleteNodes(GetNodes(context)); + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/EntityNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/EntityNode.cs new file mode 100644 index 0000000..26abb9c --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/EntityNode.cs @@ -0,0 +1,53 @@ +// 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 dnlib.DotNet; +using dnSpy.Contracts.Documents; +using dnSpy.Contracts.TreeView; + +namespace dnSpy.Analyzer.TreeNodes { + abstract class EntityNode : AnalyzerTreeNodeData, IMDTokenNode { + public abstract IMemberRef? Member { get; } + public abstract IMDTokenProvider? Reference { get; } + public SourceRef? SourceRef { get; set; } + + public override bool Activate() { + Context.AnalyzerService.OnActivated(this); + return true; + } + + public override bool HandleAssemblyListChanged(IDsDocument[] removedAssemblies, IDsDocument[] addedAssemblies) { + foreach (var asm in removedAssemblies) { + if (Member?.Module == asm.ModuleDef) + return false; // remove this node + } + HandleAssemblyListChanged(TreeNode, removedAssemblies, addedAssemblies); + return true; + } + + public override bool HandleModelUpdated(IDsDocument[] documents) { + if (Member?.Module is null) + return false; // remove this node + if ((Member is IField || Member is IMethod || Member is PropertyDef || Member is EventDef) && + Member.DeclaringType is null) + return false; + HandleModelUpdated(TreeNode, documents); + return true; + } + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/EventAccessorNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/EventAccessorNode.cs new file mode 100644 index 0000000..b580111 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/EventAccessorNode.cs @@ -0,0 +1,37 @@ +// 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 dnlib.DotNet; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Text; + +namespace dnSpy.Analyzer.TreeNodes { + sealed class EventAccessorNode : MethodNode { + readonly string? name; + + public EventAccessorNode(MethodDef analyzedMethod, string? name) + : base(analyzedMethod) => this.name = name; + + protected override void Write(ITextColorWriter output, IDecompiler decompiler) { + if (name is not null) + output.Write(BoxedTextColor.Keyword, name); + else + base.Write(output, decompiler); + } + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/EventFiredByNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/EventFiredByNode.cs new file mode 100644 index 0000000..4c44373 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/EventFiredByNode.cs @@ -0,0 +1,122 @@ +// 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 System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using dnSpy.Analyzer.Properties; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Text; + +namespace dnSpy.Analyzer.TreeNodes { + sealed class EventFiredByNode : SearchNode { + readonly List analyzedTypes; + readonly FieldDef? eventBackingField; + readonly MethodDef? eventFiringMethod; + + ConcurrentDictionary? foundMethods; + + public EventFiredByNode(EventDef analyzedEvent) { + if (analyzedEvent is null) + throw new ArgumentNullException(nameof(analyzedEvent)); + analyzedTypes = new List { analyzedEvent.DeclaringType }; + + eventBackingField = GetBackingField(analyzedEvent); + var eventType = analyzedEvent.EventType.ResolveTypeDef(); + if (eventType is not null) + eventFiringMethod = eventType.Methods.First(md => md.Name == "Invoke"); + } + + protected override void Write(ITextColorWriter output, IDecompiler decompiler) => + output.Write(BoxedTextColor.Text, dnSpy_Analyzer_Resources.RaisedByTreeNode); + + protected override IEnumerable FetchChildren(CancellationToken ct) { + foundMethods = new ConcurrentDictionary(); + + AddTypeEquivalentTypes(Context.DocumentService, analyzedTypes[0], analyzedTypes); + foreach (var declType in analyzedTypes) { + foreach (var child in FindReferencesInType(declType)) + yield return child; + } + + foundMethods = null; + } + + IEnumerable FindReferencesInType(TypeDef type) { + // HACK: in lieu of proper flow analysis, I'm going to use a simple heuristic + // If the method accesses the event's backing field, and calls invoke on a delegate + // with the same signature, then it is (most likely) raise the given event. + + foreach (MethodDef method in type.Methods) { + bool readBackingField = false; + if (!method.HasBody) + continue; + Instruction? foundInstr = null; + foreach (Instruction instr in method.Body.Instructions) { + Code code = instr.OpCode.Code; + if ((code == Code.Ldfld || code == Code.Ldflda || code == Code.Ldtoken) && instr.Operand is IField fr && fr.IsField) { + if (CheckEquals(fr.ResolveFieldDef(), eventBackingField)) { + readBackingField = true; + } + } + if (readBackingField && (code == Code.Callvirt || code == Code.Call)) { + if (instr.Operand is IMethod mr && eventFiringMethod is not null && mr.Name == eventFiringMethod.Name && CheckEquals(mr.ResolveMethodDef(), eventFiringMethod)) { + foundInstr = instr; + break; + } + } + } + + if (foundInstr is not null) { + if (GetOriginalCodeLocation(method) is MethodDef codeLocation && !HasAlreadyBeenFound(codeLocation)) { + var node = new MethodNode(codeLocation) { Context = Context }; + if (codeLocation == method) + node.SourceRef = new SourceRef(method, foundInstr.Offset, foundInstr.Operand as IMDTokenProvider); + yield return node; + } + } + } + } + + bool HasAlreadyBeenFound(MethodDef method) => !foundMethods!.TryAdd(method, 0); + + // HACK: we should probably examine add/remove methods to determine this + static FieldDef? GetBackingField(EventDef ev) { + var fieldName = ev.Name; + var vbStyleFieldName = fieldName + "Event"; + var fieldType = ev.EventType; + if (fieldType is null) + return null; + + foreach (var fd in ev.DeclaringType.Fields) { + if (fd.Name == fieldName || fd.Name == vbStyleFieldName) + if (new SigComparer().Equals(fd.FieldType, fieldType)) + return fd; + } + + return null; + } + + + public static bool CanShow(EventDef ev) => GetBackingField(ev) is not null; + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/EventNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/EventNode.cs new file mode 100644 index 0000000..f366a0f --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/EventNode.cs @@ -0,0 +1,91 @@ +// 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 System.Collections.Generic; +using dnlib.DotNet; +using dnSpy.Analyzer.Properties; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Documents.TreeView; +using dnSpy.Contracts.Images; +using dnSpy.Contracts.Text; +using dnSpy.Contracts.TreeView; + +namespace dnSpy.Analyzer.TreeNodes { + sealed class EventNode : EntityNode { + readonly EventDef analyzedEvent; + readonly bool hidesParent; + + public EventNode(EventDef analyzedEvent, bool hidesParent = false) { + this.analyzedEvent = analyzedEvent ?? throw new ArgumentNullException(nameof(analyzedEvent)); + this.hidesParent = hidesParent; + } + + public override void Initialize() => TreeNode.LazyLoading = true; + public override IMemberRef? Member => analyzedEvent; + public override IMDTokenProvider? Reference => analyzedEvent; + protected override ImageReference GetIcon(IDotNetImageService dnImgMgr) => dnImgMgr.GetImageReference(analyzedEvent); + + protected override void Write(ITextColorWriter output, IDecompiler decompiler) { + if (hidesParent) { + output.Write(BoxedTextColor.Punctuation, "("); + output.Write(BoxedTextColor.Text, dnSpy_Analyzer_Resources.HidesParent); + output.Write(BoxedTextColor.Punctuation, ")"); + output.WriteSpace(); + } + decompiler.WriteType(output, analyzedEvent.DeclaringType, true); + output.Write(BoxedTextColor.Operator, "."); + new NodeFormatter().Write(output, decompiler, analyzedEvent, Context.ShowToken); + } + + public override IEnumerable CreateChildren() { + if (analyzedEvent.AddMethod is not null) + yield return new EventAccessorNode(analyzedEvent.AddMethod, "add"); + + if (analyzedEvent.RemoveMethod is not null) + yield return new EventAccessorNode(analyzedEvent.RemoveMethod, "remove"); + + if (analyzedEvent.InvokeMethod is not null) + yield return new EventAccessorNode(analyzedEvent.InvokeMethod, "raise"); + + foreach (var accessor in analyzedEvent.OtherMethods) + yield return new EventAccessorNode(accessor, null); + + if (EventFiredByNode.CanShow(analyzedEvent)) + yield return new EventFiredByNode(analyzedEvent); + + if (EventOverriddenNode.CanShow(analyzedEvent)) + yield return new EventOverriddenNode(analyzedEvent); + + if (EventOverridesNode.CanShow(analyzedEvent)) + yield return new EventOverridesNode(analyzedEvent); + + if (InterfaceEventImplementedByNode.CanShow(analyzedEvent)) + yield return new InterfaceEventImplementedByNode(analyzedEvent); + } + + public static AnalyzerTreeNodeData? TryCreateAnalyzer(IMemberRef? member, IDecompiler decompiler) { + if (CanShow(member, decompiler)) + return new EventNode((EventDef)member!); + else + return null; + } + + public static bool CanShow(IMemberRef? member, IDecompiler decompiler) => member is EventDef; + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/EventOverriddenNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/EventOverriddenNode.cs new file mode 100644 index 0000000..add3e73 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/EventOverriddenNode.cs @@ -0,0 +1,87 @@ +/* + Copyright (C) 2017 HoLLy + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using dnlib.DotNet; +using dnSpy.Analyzer.Properties; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Text; + +namespace dnSpy.Analyzer.TreeNodes { + sealed class EventOverriddenNode : SearchNode { + readonly EventDef analyzedEvent; + readonly List analyzedTypes; + + public EventOverriddenNode(EventDef analyzedEvent) { + this.analyzedEvent = analyzedEvent ?? throw new ArgumentNullException(nameof(analyzedEvent)); + analyzedTypes = new List { analyzedEvent.DeclaringType }; + } + + protected override void Write(ITextColorWriter output, IDecompiler decompiler) => + output.Write(BoxedTextColor.Text, dnSpy_Analyzer_Resources.OverridesTreeNode); + + protected override IEnumerable FetchChildren(CancellationToken ct) { + AddTypeEquivalentTypes(Context.DocumentService, analyzedTypes[0], analyzedTypes); + foreach (var declType in analyzedTypes) { + var analyzedAccessor = GetVirtualAccessor(analyzedEvent.AddMethod) ?? GetVirtualAccessor(analyzedEvent.RemoveMethod) ?? GetVirtualAccessor(analyzedEvent.InvokeMethod); + if (analyzedAccessor?.Overrides is IList overrides && overrides.Count > 0) { + bool matched = false; + foreach (var o in overrides) { + if (o.MethodDeclaration.ResolveMethodDef() is MethodDef method && (method.IsVirtual || method.IsAbstract)) { + if (method.DeclaringType.Events.FirstOrDefault(a => (object?)a.AddMethod == method || (object?)a.RemoveMethod == method || (object?)a.InvokeMethod == method) is EventDef eventDef) { + matched = true; + yield return new EventNode(eventDef) { Context = Context }; + } + } + } + if (matched) + yield break; + } + + foreach (var eventDef in TypesHierarchyHelpers.FindBaseEvents(analyzedEvent, declType)) { + var anyAccessor = GetVirtualAccessor(eventDef.AddMethod) ?? GetVirtualAccessor(eventDef.RemoveMethod) ?? GetVirtualAccessor(eventDef.InvokeMethod); + if (anyAccessor is null || !(anyAccessor.IsVirtual || anyAccessor.IsAbstract)) + continue; + yield return new EventNode(eventDef) { Context = Context }; + yield break; + } + } + } + + public static bool CanShow(EventDef @event) => + (GetAccessor(@event.AddMethod) ?? GetAccessor(@event.RemoveMethod) ?? GetAccessor(@event.InvokeMethod)) is not null; + + static MethodDef? GetAccessor(MethodDef? accessor) { + if (accessor is not null && + accessor.DeclaringType.BaseType is not null && + (accessor.IsVirtual || accessor.IsAbstract) && accessor.IsReuseSlot) + return accessor; + return null; + } + + static MethodDef? GetVirtualAccessor(MethodDef? accessor) { + if (accessor is not null && (accessor.IsVirtual || accessor.IsAbstract)) + return accessor; + return null; + } + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/EventOverridesNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/EventOverridesNode.cs new file mode 100644 index 0000000..a43fa92 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/EventOverridesNode.cs @@ -0,0 +1,61 @@ +// 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 System.Collections.Generic; +using System.Threading; +using dnlib.DotNet; +using dnSpy.Analyzer.Properties; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Text; + +namespace dnSpy.Analyzer.TreeNodes { + sealed class EventOverridesNode : SearchNode { + readonly EventDef analyzedEvent; + + public EventOverridesNode(EventDef analyzedEvent) => this.analyzedEvent = analyzedEvent ?? throw new ArgumentNullException(nameof(analyzedEvent)); + + protected override void Write(ITextColorWriter output, IDecompiler decompiler) => + output.Write(BoxedTextColor.Text, dnSpy_Analyzer_Resources.OverriddenByTreeNode); + + protected override IEnumerable FetchChildren(CancellationToken ct) { + var analyzer = new ScopedWhereUsedAnalyzer(Context.DocumentService, analyzedEvent, FindReferencesInType); + return analyzer.PerformAnalysis(ct); + } + + IEnumerable FindReferencesInType(TypeDef type) { + if (!TypesHierarchyHelpers.IsBaseType(analyzedEvent.DeclaringType, type, resolveTypeArguments: false)) + yield break; + + foreach (EventDef eventDef in type.Events) { + if (TypesHierarchyHelpers.IsBaseEvent(analyzedEvent, eventDef)) { + MethodDef anyAccessor = eventDef.AddMethod ?? eventDef.RemoveMethod ?? eventDef.InvokeMethod; + if (anyAccessor is null) + continue; + bool hidesParent = !anyAccessor.IsVirtual ^ anyAccessor.IsNewSlot; + yield return new EventNode(eventDef, hidesParent) { Context = Context }; + } + } + } + + public static bool CanShow(EventDef @event) { + var accessor = @event.AddMethod ?? @event.RemoveMethod ?? @event.InvokeMethod; + return accessor is not null && accessor.IsVirtual && !accessor.IsFinal && !accessor.DeclaringType.IsInterface; + } + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/FieldAccessNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/FieldAccessNode.cs new file mode 100644 index 0000000..67c92b4 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/FieldAccessNode.cs @@ -0,0 +1,209 @@ +// 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 System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using dnSpy.Analyzer.Properties; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Text; + +namespace dnSpy.Analyzer.TreeNodes { + sealed class FieldAccessNode : SearchNode { + readonly bool showWrites; // true: show writes; false: show read access + readonly FieldDef analyzedField; + Lazy? foundMethods; + readonly object hashLock = new object(); + + public FieldAccessNode(FieldDef analyzedField, bool showWrites) { + this.analyzedField = analyzedField ?? throw new ArgumentNullException(nameof(analyzedField)); + this.showWrites = showWrites; + } + + protected override void Write(ITextColorWriter output, IDecompiler decompiler) => + output.Write(BoxedTextColor.Text, showWrites ? dnSpy_Analyzer_Resources.AssignedByTreeNode : dnSpy_Analyzer_Resources.ReadByTreeNode); + + protected override IEnumerable FetchChildren(CancellationToken ct) { + foundMethods = new Lazy(LazyThreadSafetyMode.ExecutionAndPublication); + + var includeAllModules = showWrites && CustomAttributesUtils.IsPseudoCustomAttributeType(analyzedField.DeclaringType); + var options = ScopedWhereUsedAnalyzerOptions.None; + if (includeAllModules) + options |= ScopedWhereUsedAnalyzerOptions.IncludeAllModules; + var analyzer = new ScopedWhereUsedAnalyzer(Context.DocumentService, analyzedField, FindReferencesInType, options); + foreach (var child in analyzer.PerformAnalysis(ct)) { + yield return child; + } + + foundMethods = null; + + if (showWrites) { + var hash = new HashSet(); + foreach (var module in analyzer.AllModules) { + if (module.Assembly is AssemblyDef asm && hash.Add(asm)) { + foreach (var node in CheckCustomAttributeNamedArgumentWrite(Context, asm, analyzedField)) + yield return node; + } + foreach (var node in CheckCustomAttributeNamedArgumentWrite(Context, module, analyzedField)) + yield return node; + } + } + } + + IEnumerable FindReferencesInType(TypeDef type) { + foreach (MethodDef method in type.Methods) { + Instruction? foundInstr = null; + if (method.HasBody) { + foreach (Instruction instr in method.Body.Instructions) { + if (CanBeReference(instr.OpCode.Code)) { + IField? fr = instr.Operand as IField; + if (CheckEquals(fr.ResolveFieldDef(), analyzedField) && + Helpers.IsReferencedBy(analyzedField.DeclaringType, fr!.DeclaringType)) { + foundInstr = instr; + break; + } + } + } + } + + if (foundInstr is not null) { + if (GetOriginalCodeLocation(method) is MethodDef codeLocation && !HasAlreadyBeenFound(codeLocation)) { + var node = new MethodNode(codeLocation) { Context = Context }; + if (codeLocation == method) + node.SourceRef = new SourceRef(method, foundInstr.Offset, foundInstr.Operand as IMDTokenProvider); + yield return node; + } + } + } + + if (showWrites) { + foreach (var node in CheckCustomAttributeNamedArgumentWrite(Context, type, analyzedField)) { + if (node is MethodNode methodNode && methodNode.Member is MethodDef method && HasAlreadyBeenFound(method)) + continue; + yield return node; + } + } + } + + internal static IEnumerable CheckCustomAttributeNamedArgumentWrite(IAnalyzerTreeNodeDataContext context, AssemblyDef asm, IMemberRef member) { + if (TryGetCAWrite(asm, member, member.IsField, out var ca)) + yield return new AssemblyNode(asm) { Context = context }; + } + + internal static IEnumerable CheckCustomAttributeNamedArgumentWrite(IAnalyzerTreeNodeDataContext context, ModuleDef module, IMemberRef member) { + if (HasWrite(module, member, member.IsField)) + yield return new ModuleNode(module) { Context = context }; + } + + internal static IEnumerable CheckCustomAttributeNamedArgumentWrite(IAnalyzerTreeNodeDataContext context, TypeDef type, IMemberRef member) { + bool isField = member.IsField; + if (HasWrite(type, member, isField)) + yield return new TypeNode(type) { Context = context }; + foreach (var method in type.Methods) { + if (HasWrite(method, member, isField)) + yield return new MethodNode(method) { Context = context }; + foreach (var pd in method.ParamDefs) { + if (HasWrite(pd, member, isField)) + yield return new MethodNode(method) { Context = context }; + } + } + foreach (var field in type.Fields) { + if (HasWrite(field, member, isField)) + yield return new FieldNode(field) { Context = context }; + } + foreach (var property in type.Properties) { + if (HasWrite(property, member, isField)) + yield return new PropertyNode(property) { Context = context }; + } + foreach (var @event in type.Events) { + if (HasWrite(@event, member, isField)) + yield return new EventNode(@event) { Context = context }; + } + } + + static bool HasWrite(IHasCustomAttribute hca, IMemberRef member, bool isField) => + TryGetCAWrite(hca, member, isField, out _); + + static bool TryGetCAWrite(IHasCustomAttribute hca, IMemberRef member, bool isField, [NotNullWhen(true)] out CustomAttribute? customAttribute) { + customAttribute = null; + TypeSig? memberType; + if (isField) + memberType = ((IField)member).FieldSig?.GetFieldType(); + else { + var property = (PropertyDef)member; + var propSig = property.PropertySig; + if (propSig is null || propSig.Params.Count != 0) + return false; + memberType = propSig?.GetRetType(); + } + foreach (var ca in hca.GetCustomAttributes()) { + if (!new SigComparer().Equals(ca.AttributeType.GetScopeType(), member.DeclaringType)) + continue; + var namedArgs = ca.NamedArguments; + for (int i = 0; i < namedArgs.Count; i++) { + var namedArg = namedArgs[i]; + if (namedArg.IsField != isField) + continue; + if (namedArg.Name != member.Name) + continue; + if (!new SigComparer().Equals(namedArg.Type, memberType)) + continue; + + customAttribute = ca; + return true; + } + } + + return false; + } + + bool CanBeReference(Code code) { + switch (code) { + case Code.Ldfld: + case Code.Ldsfld: + return !showWrites; + case Code.Stfld: + case Code.Stsfld: + return showWrites; + case Code.Ldflda: + case Code.Ldsflda: + case Code.Ldtoken: + return true; // always show address-loading + default: + return false; + } + } + + bool HasAlreadyBeenFound(MethodDef method) { + Hashtable hashtable = foundMethods!.Value; + lock (hashLock) { + if (hashtable.Contains(method)) { + return true; + } + else { + hashtable.Add(method, null); + return false; + } + } + } + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/FieldNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/FieldNode.cs new file mode 100644 index 0000000..84d6530 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/FieldNode.cs @@ -0,0 +1,52 @@ +// 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 System.Collections.Generic; +using dnlib.DotNet; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Documents.TreeView; +using dnSpy.Contracts.Images; +using dnSpy.Contracts.Text; +using dnSpy.Contracts.TreeView; + +namespace dnSpy.Analyzer.TreeNodes { + sealed class FieldNode : EntityNode { + readonly FieldDef analyzedField; + + public FieldNode(FieldDef analyzedField) => this.analyzedField = analyzedField ?? throw new ArgumentNullException(nameof(analyzedField)); + + public override void Initialize() => TreeNode.LazyLoading = true; + protected override ImageReference GetIcon(IDotNetImageService dnImgMgr) => dnImgMgr.GetImageReference(analyzedField); + + protected override void Write(ITextColorWriter output, IDecompiler decompiler) { + decompiler.WriteType(output, analyzedField.DeclaringType, true); + output.Write(BoxedTextColor.Operator, "."); + new NodeFormatter().Write(output, decompiler, analyzedField, Context.ShowToken); + } + + public override IEnumerable CreateChildren() { + yield return new FieldAccessNode(analyzedField, false); + if (!analyzedField.IsLiteral) + yield return new FieldAccessNode(analyzedField, true); + } + + public override IMemberRef? Member => analyzedField; + public override IMDTokenProvider? Reference => analyzedField; + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/Helpers.cs b/Extensions/dnSpy.Analyzer/TreeNodes/Helpers.cs new file mode 100644 index 0000000..2a00b5f --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/Helpers.cs @@ -0,0 +1,118 @@ +// 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.Linq; +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using dnSpy.Contracts.Decompiler; + +namespace dnSpy.Analyzer.TreeNodes { + static class Helpers { + public static bool IsReferencedBy(TypeDef? type, ITypeDefOrRef? typeRef) => new SigComparer().Equals(type, typeRef.GetScopeType()); + + public static IMemberRef GetOriginalCodeLocation(IMemberRef member) { + if (member is MethodDef) + return GetOriginalCodeLocation((MethodDef)member); + return member; + } + + static MethodDef GetOriginalCodeLocation(MethodDef method) { + if (IsCompilerGenerated(method)) { + return FindMethodUsageInType(method.DeclaringType, method) ?? method; + } + + var typeUsage = GetOriginalCodeLocation(method.DeclaringType); + + return typeUsage ?? method; + } + + /// + /// Given a compiler-generated type, returns the method where that type is used. + /// Used to detect the 'parent method' for a lambda/iterator/async state machine. + /// + static MethodDef? GetOriginalCodeLocation(TypeDef type) { + if (type is not null && type.DeclaringType is not null && IsCompilerGenerated(type)) { + if (type.IsValueType) { + // Value types might not have any constructor; but they must be stored in a local var + // because 'initobj' (or 'call .ctor') expects a managed ref. + return FindVariableOfTypeUsageInType(type.DeclaringType, type); + } + else { + var constructor = GetTypeConstructor(type); + if (constructor is null) + return null; + return FindMethodUsageInType(type.DeclaringType, constructor); + } + } + return null; + } + + static MethodDef? GetTypeConstructor(TypeDef type) => type.FindConstructors().FirstOrDefault(); + + static MethodDef? FindMethodUsageInType(TypeDef type, MethodDef analyzedMethod) { + string name = analyzedMethod.Name; + foreach (MethodDef method in type.Methods) { + bool found = false; + if (!method.HasBody) + continue; + foreach (Instruction instr in method.Body.Instructions) { + if (instr.Operand is IMethod mr && !mr.IsField && mr.Name == name && + IsReferencedBy(analyzedMethod.DeclaringType, mr.DeclaringType) && + CheckEquals(mr.ResolveMethodDef(), analyzedMethod)) { + found = true; + break; + } + } + + if (found) + return method; + } + return null; + } + + internal static bool CheckEquals(IMemberRef? mr1, IMemberRef? mr2) => + new SigComparer(SigComparerOptions.CompareDeclaringTypes | SigComparerOptions.PrivateScopeIsComparable).Equals(mr1, mr2); + + static MethodDef? FindVariableOfTypeUsageInType(TypeDef type, TypeDef variableType) { + foreach (MethodDef method in type.Methods) { + bool found = false; + if (!method.HasBody) + continue; + foreach (var v in method.Body.Variables) { + if (ResolveWithinSameModule(v.Type.ToTypeDefOrRef()) == variableType) { + found = true; + break; + } + } + + if (found) + return method; + } + return null; + } + + static TypeDef? ResolveWithinSameModule(ITypeDefOrRef type) { + if (type is not null && type.Scope == type.Module) + return type.ResolveTypeDef(); + return null; + } + + static bool IsCompilerGenerated(this IHasCustomAttribute hca) => + hca is not null && hca.CustomAttributes.IsDefined("System.Runtime.CompilerServices.CompilerGeneratedAttribute"); + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/IAnalyzerTreeNodeDataContext.cs b/Extensions/dnSpy.Analyzer/TreeNodes/IAnalyzerTreeNodeDataContext.cs new file mode 100644 index 0000000..b402098 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/IAnalyzerTreeNodeDataContext.cs @@ -0,0 +1,38 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Documents; +using dnSpy.Contracts.Images; +using dnSpy.Contracts.TreeView; +using dnSpy.Contracts.TreeView.Text; + +namespace dnSpy.Analyzer.TreeNodes { + interface IAnalyzerTreeNodeDataContext { + bool SingleClickExpandsChildren { get; } + bool SyntaxHighlight { get; } + bool ShowToken { get; } + ITreeView TreeView { get; } + IDecompiler Decompiler { get; } + ITreeViewNodeTextElementProvider TreeViewNodeTextElementProvider { get; } + IDotNetImageService DotNetImageService { get; } + IDsDocumentService DocumentService { get; } + IAnalyzerService AnalyzerService { get; } + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/IAsyncCancellable.cs b/Extensions/dnSpy.Analyzer/TreeNodes/IAsyncCancellable.cs new file mode 100644 index 0000000..1b06d88 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/IAsyncCancellable.cs @@ -0,0 +1,24 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +namespace dnSpy.Analyzer.TreeNodes { + interface IAsyncCancellable { + void Cancel(); + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/InterfaceEventImplementedByNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/InterfaceEventImplementedByNode.cs new file mode 100644 index 0000000..8ac4844 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/InterfaceEventImplementedByNode.cs @@ -0,0 +1,111 @@ +// 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 System.Collections.Generic; +using System.Linq; +using System.Threading; +using dnlib.DotNet; +using dnSpy.Analyzer.Properties; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Text; + +namespace dnSpy.Analyzer.TreeNodes { + sealed class InterfaceEventImplementedByNode : SearchNode { + readonly EventDef analyzedEvent; + readonly MethodDef? analyzedMethod; + readonly AccessorKind accessorKind; + + enum AccessorKind { + Adder, + Remover, + Invoker, + } + + public InterfaceEventImplementedByNode(EventDef analyzedEvent) { + this.analyzedEvent = analyzedEvent ?? throw new ArgumentNullException(nameof(analyzedEvent)); + if (this.analyzedEvent.AddMethod is not null) { + analyzedMethod = this.analyzedEvent.AddMethod; + accessorKind = AccessorKind.Adder; + } + else if (this.analyzedEvent.RemoveMethod is not null) { + analyzedMethod = this.analyzedEvent.RemoveMethod; + accessorKind = AccessorKind.Remover; + } + else { + analyzedMethod = this.analyzedEvent.InvokeMethod; + accessorKind = AccessorKind.Invoker; + } + } + + protected override void Write(ITextColorWriter output, IDecompiler decompiler) => + output.Write(BoxedTextColor.Text, dnSpy_Analyzer_Resources.ImplementedByTreeNode); + + protected override IEnumerable FetchChildren(CancellationToken ct) { + if (analyzedMethod is null) + yield break; + var analyzer = new ScopedWhereUsedAnalyzer(Context.DocumentService, analyzedMethod, FindReferencesInType); + foreach (var child in analyzer.PerformAnalysis(ct)) { + yield return child; + } + } + + IEnumerable FindReferencesInType(TypeDef type) { + if (analyzedMethod is null) + yield break; + if (type.IsInterface) + yield break; + var implementedInterfaceRef = InterfaceMethodImplementedByNode.GetInterface(type, analyzedMethod.DeclaringType); + if (implementedInterfaceRef is null) + yield break; + + foreach (EventDef ev in type.Events.Where(e => e.Name.EndsWith(analyzedEvent.Name))) { + var accessor = GetAccessor(ev); + // Don't include abstract accessors, they don't implement anything + if (accessor is null || !accessor.IsVirtual || accessor.IsAbstract) + continue; + if (accessor.HasOverrides && accessor.Overrides.Any(m => CheckEquals(m.MethodDeclaration.ResolveMethodDef(), analyzedMethod))) { + yield return new EventNode(ev) { Context = Context }; + yield break; + } + } + + foreach (EventDef ev in type.Events.Where(e => e.Name == analyzedEvent.Name)) { + var accessor = GetAccessor(ev); + // Don't include abstract accessors, they don't implement anything + if (accessor is null || !accessor.IsVirtual || accessor.IsAbstract) + continue; + if (TypesHierarchyHelpers.MatchInterfaceMethod(accessor, analyzedMethod, implementedInterfaceRef)) { + yield return new EventNode(ev) { Context = Context }; + yield break; + } + } + } + + MethodDef? GetAccessor(EventDef ev) { + switch (accessorKind) { + case AccessorKind.Adder: return ev.AddMethod; + case AccessorKind.Remover: return ev.RemoveMethod; + case AccessorKind.Invoker: return ev.InvokeMethod; + default: return null; + } + } + + public static bool CanShow(EventDef ev) => ev.DeclaringType.IsInterface && (ev.AddMethod ?? ev.RemoveMethod ?? ev.InvokeMethod) is MethodDef accessor && (accessor.IsVirtual || accessor.IsAbstract); + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/InterfaceMethodImplementedByNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/InterfaceMethodImplementedByNode.cs new file mode 100644 index 0000000..f255c15 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/InterfaceMethodImplementedByNode.cs @@ -0,0 +1,138 @@ +// 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 System.Collections.Generic; +using System.Linq; +using System.Threading; +using dnlib.DotNet; +using dnSpy.Analyzer.Properties; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Text; + +namespace dnSpy.Analyzer.TreeNodes { + sealed class InterfaceMethodImplementedByNode : SearchNode { + readonly MethodDef analyzedMethod; + Guid comGuid; + bool isComType; + int vtblIndex; + + public InterfaceMethodImplementedByNode(MethodDef analyzedMethod) => this.analyzedMethod = analyzedMethod ?? throw new ArgumentNullException(nameof(analyzedMethod)); + + protected override void Write(ITextColorWriter output, IDecompiler decompiler) => + output.Write(BoxedTextColor.Text, dnSpy_Analyzer_Resources.ImplementedByTreeNode); + + protected override IEnumerable FetchChildren(CancellationToken ct) { + ComUtils.GetMemberInfo(analyzedMethod, out isComType, out comGuid, out vtblIndex); + bool includeAllModules = isComType; + var options = ScopedWhereUsedAnalyzerOptions.None; + if (includeAllModules) + options |= ScopedWhereUsedAnalyzerOptions.IncludeAllModules; + if (isComType) + options |= ScopedWhereUsedAnalyzerOptions.ForcePublic; + var analyzer = new ScopedWhereUsedAnalyzer(Context.DocumentService, analyzedMethod, FindReferencesInType, options); + return analyzer.PerformAnalysis(ct); + } + + IEnumerable FindReferencesInType(TypeDef type) { + if (type.IsInterface) + yield break; + var implementedInterfaceRef = GetInterface(type, analyzedMethod.DeclaringType); + var comIface = isComType ? GetComInterface(type, ref comGuid) : null; + + foreach (var method in type.Methods) { + // Don't include abstract methods, they don't implement anything + if (!method.IsVirtual || method.IsAbstract) + continue; + if (method.HasOverrides && method.Overrides.Any(m => CheckOverride(m))) { + yield return new MethodNode(method) { Context = Context }; + yield break; + } + } + + if (comIface is not null && ComUtils.GetMethod(comIface, vtblIndex) is MethodDef comIfaceMethod) { + foreach (var method in type.Methods) { + // Don't include abstract methods, they don't implement anything + if (!method.IsVirtual || method.IsAbstract) + continue; + if (TypesHierarchyHelpers.MatchInterfaceMethod(method, comIfaceMethod, comIface)) { + yield return new MethodNode(method) { Context = Context }; + yield break; + } + } + } + + if (implementedInterfaceRef is not null) { + foreach (var method in type.Methods) { + // Don't include abstract methods, they don't implement anything + if (!method.IsVirtual || method.IsAbstract) + continue; + if (method.Name != analyzedMethod.Name) + continue; + if (TypesHierarchyHelpers.MatchInterfaceMethod(method, analyzedMethod, implementedInterfaceRef)) { + yield return new MethodNode(method) { Context = Context }; + yield break; + } + } + } + } + + bool CheckOverride(MethodOverride m) { + if (!(m.MethodDeclaration.ResolveMethodDef() is MethodDef method)) + return false; + if (isComType) { + ComUtils.GetMemberInfo(method, out bool otherIsComType, out var otherComGuid, out int otherVtblIndex); + if (otherIsComType && otherComGuid == comGuid && otherVtblIndex == vtblIndex) + return true; + } + return CheckEquals(method, analyzedMethod); + } + + internal static ITypeDefOrRef? GetInterface(TypeDef type, TypeDef interfaceType) { + foreach (var t in TypesHierarchyHelpers.GetTypeAndBaseTypes(type)) { + var td = t.Resolve(); + if (td is null) + break; + foreach (var ii in td.Interfaces) { + var genericArgs = t is GenericInstSig ? ((GenericInstSig)t).GenericArguments : null; + var iface = GenericArgumentResolver.Resolve(ii.Interface.ToTypeSig(), genericArgs, null); + if (iface is null) + continue; + if (new SigComparer().Equals(ii.Interface.GetScopeType(), interfaceType)) + return iface.ToTypeDefOrRef(); + } + } + return null; + } + + static TypeDef? GetComInterface(TypeDef type, ref Guid comGuid) { + foreach (var t in TypesHierarchyHelpers.GetTypeAndBaseTypes(type)) { + var td = t.Resolve(); + if (td is null) + break; + foreach (var ii in td.Interfaces) { + if (ii.Interface.GetScopeType().ResolveTypeDef() is TypeDef iface && ComUtils.ComEquals(iface, ref comGuid)) + return iface; + } + } + return null; + } + + public static bool CanShow(MethodDef method) => method.DeclaringType.IsInterface && (method.IsVirtual || method.IsAbstract); + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/InterfacePropertyImplementedByNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/InterfacePropertyImplementedByNode.cs new file mode 100644 index 0000000..0948962 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/InterfacePropertyImplementedByNode.cs @@ -0,0 +1,90 @@ +// 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 System.Collections.Generic; +using System.Linq; +using System.Threading; +using dnlib.DotNet; +using dnSpy.Analyzer.Properties; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Text; + +namespace dnSpy.Analyzer.TreeNodes { + sealed class InterfacePropertyImplementedByNode : SearchNode { + readonly PropertyDef analyzedProperty; + readonly MethodDef? analyzedMethod; + readonly bool isGetter; + + public InterfacePropertyImplementedByNode(PropertyDef analyzedProperty) { + this.analyzedProperty = analyzedProperty ?? throw new ArgumentNullException(nameof(analyzedProperty)); + if (this.analyzedProperty.GetMethod is not null) { + analyzedMethod = this.analyzedProperty.GetMethod; + isGetter = true; + } + else { + analyzedMethod = this.analyzedProperty.SetMethod; + isGetter = false; + } + } + + protected override void Write(ITextColorWriter output, IDecompiler decompiler) => + output.Write(BoxedTextColor.Text, dnSpy_Analyzer_Resources.ImplementedByTreeNode); + + protected override IEnumerable FetchChildren(CancellationToken ct) { + if (analyzedMethod is null) + return new List(); + var analyzer = new ScopedWhereUsedAnalyzer(Context.DocumentService, analyzedMethod, FindReferencesInType); + return analyzer.PerformAnalysis(ct); + } + + IEnumerable FindReferencesInType(TypeDef type) { + if (analyzedMethod is null) + yield break; + if (type.IsInterface) + yield break; + var implementedInterfaceRef = InterfaceMethodImplementedByNode.GetInterface(type, analyzedMethod.DeclaringType); + if (implementedInterfaceRef is null) + yield break; + + foreach (PropertyDef property in type.Properties.Where(e => e.Name.EndsWith(analyzedProperty.Name))) { + MethodDef accessor = isGetter ? property.GetMethod : property.SetMethod; + // Don't include abstract accessors, they don't implement anything + if (accessor is null || !accessor.IsVirtual || accessor.IsAbstract) + continue; + if (accessor.HasOverrides && accessor.Overrides.Any(m => CheckEquals(m.MethodDeclaration.ResolveMethodDef(), analyzedMethod))) { + yield return new PropertyNode(property) { Context = Context }; + yield break; + } + } + + foreach (PropertyDef property in type.Properties.Where(e => e.Name == analyzedProperty.Name)) { + MethodDef accessor = isGetter ? property.GetMethod : property.SetMethod; + // Don't include abstract accessors, they don't implement anything + if (accessor is null || !accessor.IsVirtual || accessor.IsAbstract) + continue; + if (TypesHierarchyHelpers.MatchInterfaceMethod(accessor, analyzedMethod, implementedInterfaceRef)) { + yield return new PropertyNode(property) { Context = Context }; + yield break; + } + } + } + + public static bool CanShow(PropertyDef property) => property.DeclaringType.IsInterface && (property.GetMethod ?? property.SetMethod) is MethodDef accessor && (accessor.IsVirtual || accessor.IsAbstract); + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/MethodNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/MethodNode.cs new file mode 100644 index 0000000..f71c1cc --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/MethodNode.cs @@ -0,0 +1,78 @@ +// 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 System.Collections.Generic; +using dnlib.DotNet; +using dnSpy.Analyzer.Properties; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Documents.TreeView; +using dnSpy.Contracts.Images; +using dnSpy.Contracts.Text; +using dnSpy.Contracts.TreeView; + +namespace dnSpy.Analyzer.TreeNodes { + class MethodNode : EntityNode { + readonly MethodDef analyzedMethod; + readonly bool hidesParent; + readonly bool isSetter; + + public MethodNode(MethodDef analyzedMethod, bool hidesParent = false, bool isSetter = false) { + this.analyzedMethod = analyzedMethod ?? throw new ArgumentNullException(nameof(analyzedMethod)); + this.hidesParent = hidesParent; + this.isSetter = isSetter; + } + + public override void Initialize() => TreeNode.LazyLoading = true; + protected override ImageReference GetIcon(IDotNetImageService dnImgMgr) => dnImgMgr.GetImageReference(analyzedMethod); + + protected override void Write(ITextColorWriter output, IDecompiler decompiler) { + if (hidesParent) { + output.Write(BoxedTextColor.Punctuation, "("); + output.Write(BoxedTextColor.Text, dnSpy_Analyzer_Resources.HidesParent); + output.Write(BoxedTextColor.Punctuation, ")"); + output.WriteSpace(); + } + decompiler.WriteType(output, analyzedMethod.DeclaringType, true); + output.Write(BoxedTextColor.Operator, "."); + new NodeFormatter().Write(output, decompiler, analyzedMethod, Context.ShowToken); + } + + public override IEnumerable CreateChildren() { + if (analyzedMethod.HasBody) + yield return new MethodUsesNode(analyzedMethod); + + if ((analyzedMethod.IsVirtual || analyzedMethod.IsAbstract) && !(analyzedMethod.IsNewSlot && analyzedMethod.IsFinal)) + yield return new VirtualMethodUsedByNode(analyzedMethod, isSetter); + else + yield return new MethodUsedByNode(analyzedMethod, isSetter); + + if (MethodOverriddenNode.CanShow(analyzedMethod)) + yield return new MethodOverriddenNode(analyzedMethod); + + if (MethodOverridesNode.CanShow(analyzedMethod)) + yield return new MethodOverridesNode(analyzedMethod); + + if (InterfaceMethodImplementedByNode.CanShow(analyzedMethod)) + yield return new InterfaceMethodImplementedByNode(analyzedMethod); + } + + public override IMemberRef? Member => analyzedMethod; + public override IMDTokenProvider? Reference => analyzedMethod; + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/MethodOverriddenNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/MethodOverriddenNode.cs new file mode 100644 index 0000000..c833dbf --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/MethodOverriddenNode.cs @@ -0,0 +1,72 @@ +/* + Copyright (C) 2017 HoLLy + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; +using System.Collections.Generic; +using System.Threading; +using dnlib.DotNet; +using dnSpy.Analyzer.Properties; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Text; + +namespace dnSpy.Analyzer.TreeNodes { + /// + /// Searches methods that are overridden by the analyzed method. + /// + sealed class MethodOverriddenNode : SearchNode { + readonly MethodDef analyzedMethod; + readonly List analyzedTypes; + + public MethodOverriddenNode(MethodDef analyzedMethod) { + this.analyzedMethod = analyzedMethod ?? throw new ArgumentNullException(nameof(analyzedMethod)); + analyzedTypes = new List { analyzedMethod.DeclaringType }; + } + + protected override void Write(ITextColorWriter output, IDecompiler decompiler) => + output.Write(BoxedTextColor.Text, dnSpy_Analyzer_Resources.OverridesTreeNode); + + protected override IEnumerable FetchChildren(CancellationToken ct) { + AddTypeEquivalentTypes(Context.DocumentService, analyzedTypes[0], analyzedTypes); + var overrides = analyzedMethod.Overrides; + foreach (var declType in analyzedTypes) { + if (overrides.Count > 0) { + bool matched = false; + foreach (var o in overrides) { + if (o.MethodDeclaration.ResolveMethodDef() is MethodDef method && (method.IsVirtual || method.IsAbstract)) { + matched = true; + yield return new MethodNode(method) { Context = Context }; + } + } + if (matched) + yield break; + } + foreach (var method in TypesHierarchyHelpers.FindBaseMethods(analyzedMethod, declType)) { + if (!(method.IsVirtual || method.IsAbstract)) + continue; + yield return new MethodNode(method) { Context = Context }; + yield break; + } + } + } + + public static bool CanShow(MethodDef method) => + method.DeclaringType.BaseType is not null && + (method.IsVirtual || method.IsAbstract) && method.IsReuseSlot; + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/MethodOverridesNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/MethodOverridesNode.cs new file mode 100644 index 0000000..ce8a454 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/MethodOverridesNode.cs @@ -0,0 +1,62 @@ +// 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 System.Collections.Generic; +using System.Threading; +using dnlib.DotNet; +using dnSpy.Analyzer.Properties; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Text; + +namespace dnSpy.Analyzer.TreeNodes { + /// + /// Searches for overrides of the analyzed method. + /// + sealed class MethodOverridesNode : SearchNode { + readonly MethodDef analyzedMethod; + + public MethodOverridesNode(MethodDef analyzedMethod) => this.analyzedMethod = analyzedMethod ?? throw new ArgumentNullException(nameof(analyzedMethod)); + + protected override void Write(ITextColorWriter output, IDecompiler decompiler) => + output.Write(BoxedTextColor.Text, dnSpy_Analyzer_Resources.OverriddenByTreeNode); + + protected override IEnumerable FetchChildren(CancellationToken ct) { + var analyzer = new ScopedWhereUsedAnalyzer(Context.DocumentService, analyzedMethod, FindReferencesInType); + return analyzer.PerformAnalysis(ct); + } + + IEnumerable FindReferencesInType(TypeDef type) { + if (!TypesHierarchyHelpers.IsBaseType(analyzedMethod.DeclaringType, type, resolveTypeArguments: false)) + yield break; + foreach (var method in type.Methods) { + if (TypesHierarchyHelpers.IsBaseMethod(analyzedMethod, method)) { + bool hidesParent = !method.IsVirtual ^ method.IsNewSlot; + yield return new MethodNode(method, hidesParent) { Context = Context }; + yield break; + } + } + } + + public static bool CanShow(MethodDef method) => + method.IsVirtual && + !method.IsFinal && + !method.DeclaringType.IsSealed && + !method.DeclaringType.IsInterface; // interface methods are definitions not implementations - cannot be overridden + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/MethodUsedByNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/MethodUsedByNode.cs new file mode 100644 index 0000000..03a16ba --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/MethodUsedByNode.cs @@ -0,0 +1,161 @@ +// 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 System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using dnSpy.Analyzer.Properties; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Text; + +namespace dnSpy.Analyzer.TreeNodes { + sealed class MethodUsedByNode : SearchNode { + readonly MethodDef analyzedMethod; + readonly bool isSetter; + readonly UTF8String? implMapName; + readonly string? implMapModule; + PropertyDef? property; + ConcurrentDictionary? foundMethods; + + public MethodUsedByNode(MethodDef analyzedMethod, bool isSetter) { + this.analyzedMethod = analyzedMethod ?? throw new ArgumentNullException(nameof(analyzedMethod)); + this.isSetter = isSetter; + if (analyzedMethod.ImplMap is ImplMap implMap) { + implMapName = GetDllImportMethodName(analyzedMethod, implMap); + implMapModule = NormalizeModuleName(implMap.Module?.Name); + } + } + + static UTF8String GetDllImportMethodName(MethodDef method, ImplMap implMap) { + if (!UTF8String.IsNullOrEmpty(implMap.Name)) + return implMap.Name; + return method.Name; + } + + static string NormalizeModuleName(string name) { + if (string.IsNullOrEmpty(name)) + return string.Empty; + if (name.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) + name = name.Substring(0, name.Length - 4); + else { + if (name.StartsWith("lib", StringComparison.Ordinal)) + name = name.Substring(3); + + if (name.EndsWith(".so", StringComparison.Ordinal)) + name = name.Substring(0, name.Length - 3); + else if (name.EndsWith(".dylib", StringComparison.OrdinalIgnoreCase)) + name = name.Substring(0, name.Length - 6); + else if (name.EndsWith(".a", StringComparison.Ordinal)) + name = name.Substring(0, name.Length - 2); + else if (name.EndsWith(".sl", StringComparison.Ordinal)) + name = name.Substring(0, name.Length - 3); + } + return name; + } + + protected override void Write(ITextColorWriter output, IDecompiler decompiler) => + output.Write(BoxedTextColor.Text, dnSpy_Analyzer_Resources.UsedByTreeNode); + + protected override IEnumerable FetchChildren(CancellationToken ct) { + foundMethods = new ConcurrentDictionary(); + + if (isSetter) + property = analyzedMethod.DeclaringType.Properties.FirstOrDefault(a => a.SetMethod == analyzedMethod); + + var includeAllModules = (property is not null && CustomAttributesUtils.IsPseudoCustomAttributeType(analyzedMethod.DeclaringType)) || implMapName is not null; + var options = ScopedWhereUsedAnalyzerOptions.None; + if (includeAllModules) + options |= ScopedWhereUsedAnalyzerOptions.IncludeAllModules; + if (implMapName is not null) + options |= ScopedWhereUsedAnalyzerOptions.ForcePublic; + var analyzer = new ScopedWhereUsedAnalyzer(Context.DocumentService, analyzedMethod, FindReferencesInType, options); + foreach (var child in analyzer.PerformAnalysis(ct)) { + yield return child; + } + + if (property is not null) { + var hash = new HashSet(); + foreach (var module in analyzer.AllModules) { + if (module.Assembly is AssemblyDef asm && hash.Add(asm)) { + foreach (var node in FieldAccessNode.CheckCustomAttributeNamedArgumentWrite(Context, asm, property)) + yield return node; + } + foreach (var node in FieldAccessNode.CheckCustomAttributeNamedArgumentWrite(Context, module, property)) + yield return node; + } + } + + foundMethods = null; + } + + IEnumerable FindReferencesInType(TypeDef type) { + string name = analyzedMethod.Name; + foreach (MethodDef method in type.Methods) { + if (!method.HasBody) + continue; + Instruction? foundInstr = null; + if (implMapName is not null) { + foreach (var instr in method.Body.Instructions) { + if (instr.Operand is IMethod mr && !mr.IsField && + mr.ResolveMethodDef() is MethodDef md && + // DllImport methods are the same if module + func name are identical + md?.ImplMap is ImplMap otherImplMap && + implMapName == GetDllImportMethodName(md, otherImplMap) && + StringComparer.OrdinalIgnoreCase.Equals(implMapModule, NormalizeModuleName(otherImplMap.Module?.Name))) { + foundInstr = instr; + break; + } + } + } + else { + foreach (var instr in method.Body.Instructions) { + if (instr.Operand is IMethod mr && !mr.IsField && mr.Name == name && + Helpers.IsReferencedBy(analyzedMethod.DeclaringType, mr.DeclaringType) && + CheckEquals(mr.ResolveMethodDef(), analyzedMethod)) { + foundInstr = instr; + break; + } + } + } + + if (foundInstr is not null) { + if (GetOriginalCodeLocation(method) is MethodDef codeLocation && !HasAlreadyBeenFound(codeLocation)) { + var node = new MethodNode(codeLocation) { Context = Context }; + if (codeLocation == method) + node.SourceRef = new SourceRef(method, foundInstr.Offset, foundInstr.Operand as IMDTokenProvider); + yield return node; + } + } + } + + if (property is not null) { + foreach (var node in FieldAccessNode.CheckCustomAttributeNamedArgumentWrite(Context, type, property)) { + if (node is MethodNode methodNode && methodNode.Member is MethodDef method && HasAlreadyBeenFound(method)) + continue; + yield return node; + } + } + } + + bool HasAlreadyBeenFound(MethodDef method) => !foundMethods!.TryAdd(method, 0); + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/MethodUsesNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/MethodUsesNode.cs new file mode 100644 index 0000000..840061f --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/MethodUsesNode.cs @@ -0,0 +1,79 @@ +// 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 System.Collections.Generic; +using System.Linq; +using System.Threading; +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using dnSpy.Analyzer.Properties; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Text; + +namespace dnSpy.Analyzer.TreeNodes { + /// + /// Shows the methods that are used by this method. + /// + sealed class MethodUsesNode : SearchNode { + readonly MethodDef analyzedMethod; + + public MethodUsesNode(MethodDef analyzedMethod) => this.analyzedMethod = analyzedMethod ?? throw new ArgumentNullException(nameof(analyzedMethod)); + + protected override void Write(ITextColorWriter output, IDecompiler decompiler) => + output.Write(BoxedTextColor.Text, dnSpy_Analyzer_Resources.UsesTreeNode); + + readonly struct DefRef where T : IDnlibDef { + public readonly T Def; + public readonly SourceRef SourceRef; + public DefRef(T def, SourceRef sourceRef) { + Def = def; + SourceRef = sourceRef; + } + } + + protected override IEnumerable FetchChildren(CancellationToken ct) { + foreach (var f in GetUsedFields().Distinct()) { + yield return new FieldNode(f.Def) { Context = Context, SourceRef = f.SourceRef }; + } + foreach (var m in GetUsedMethods().Distinct()) { + yield return new MethodNode(m.Def) { Context = Context, SourceRef = m.SourceRef }; + } + } + + IEnumerable> GetUsedMethods() { + foreach (Instruction instr in analyzedMethod.Body.Instructions) { + if (instr.Operand is IMethod mr && !mr.IsField) { + MethodDef def = mr.ResolveMethodDef(); + if (def is not null) + yield return new DefRef(def, new SourceRef(analyzedMethod, instr.Offset, instr.Operand as IMDTokenProvider)); + } + } + } + + IEnumerable> GetUsedFields() { + foreach (Instruction instr in analyzedMethod.Body.Instructions) { + if (instr.Operand is IField fr && !fr.IsMethod) { + FieldDef def = fr.ResolveFieldDef(); + if (def is not null) + yield return new DefRef(def, new SourceRef(analyzedMethod, instr.Offset, instr.Operand as IMDTokenProvider)); + } + } + } + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/ModuleNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/ModuleNode.cs new file mode 100644 index 0000000..2459855 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/ModuleNode.cs @@ -0,0 +1,38 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using dnlib.DotNet; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Images; +using dnSpy.Contracts.Text; + +namespace dnSpy.Analyzer.TreeNodes { + sealed class ModuleNode : EntityNode { + readonly ModuleDef module; + + public override IMemberRef? Member => null; + public override IMDTokenProvider? Reference => module; + + public ModuleNode(ModuleDef module) => this.module = module; + + protected override ImageReference GetIcon(IDotNetImageService dnImgMgr) => dnImgMgr.GetImageReference(module); + protected override void Write(ITextColorWriter output, IDecompiler decompiler) => + output.Write(BoxedTextColor.AssemblyModule, NameUtilities.CleanIdentifier(module.Name)); + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/PropertyAccessorNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/PropertyAccessorNode.cs new file mode 100644 index 0000000..5f71466 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/PropertyAccessorNode.cs @@ -0,0 +1,37 @@ +// 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 dnlib.DotNet; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Text; + +namespace dnSpy.Analyzer.TreeNodes { + sealed class PropertyAccessorNode : MethodNode { + readonly string? name; + + public PropertyAccessorNode(MethodDef analyzedMethod, string? name, bool isSetter) + : base(analyzedMethod, isSetter: isSetter) => this.name = name; + + protected override void Write(ITextColorWriter output, IDecompiler decompiler) { + if (name is not null) + output.Write(BoxedTextColor.Keyword, name); + else + base.Write(output, decompiler); + } + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/PropertyNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/PropertyNode.cs new file mode 100644 index 0000000..380d693 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/PropertyNode.cs @@ -0,0 +1,89 @@ +// 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 System.Collections.Generic; +using dnlib.DotNet; +using dnSpy.Analyzer.Properties; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Documents.TreeView; +using dnSpy.Contracts.Images; +using dnSpy.Contracts.Text; +using dnSpy.Contracts.TreeView; + +namespace dnSpy.Analyzer.TreeNodes { + sealed class PropertyNode : EntityNode { + readonly PropertyDef analyzedProperty; + readonly bool isIndexer; + readonly bool hidesParent; + + public PropertyNode(PropertyDef analyzedProperty, bool hidesParent = false) { + if (analyzedProperty is null) + throw new ArgumentNullException(nameof(analyzedProperty)); + isIndexer = analyzedProperty.IsIndexer(); + this.analyzedProperty = analyzedProperty; + this.hidesParent = hidesParent; + } + + public override void Initialize() => TreeNode.LazyLoading = true; + protected override ImageReference GetIcon(IDotNetImageService dnImgMgr) => dnImgMgr.GetImageReference(analyzedProperty); + + protected override void Write(ITextColorWriter output, IDecompiler decompiler) { + if (hidesParent) { + output.Write(BoxedTextColor.Punctuation, "("); + output.Write(BoxedTextColor.Text, dnSpy_Analyzer_Resources.HidesParent); + output.Write(BoxedTextColor.Punctuation, ")"); + output.WriteSpace(); + } + decompiler.WriteType(output, analyzedProperty.DeclaringType, true); + output.Write(BoxedTextColor.Operator, "."); + new NodeFormatter().Write(output, decompiler, analyzedProperty, Context.ShowToken, null); + } + + public override IEnumerable CreateChildren() { + if (analyzedProperty.GetMethod is not null) + yield return new PropertyAccessorNode(analyzedProperty.GetMethod, "get", isSetter: false); + + if (analyzedProperty.SetMethod is not null) + yield return new PropertyAccessorNode(analyzedProperty.SetMethod, "set", isSetter: true); + + foreach (var accessor in analyzedProperty.OtherMethods) + yield return new PropertyAccessorNode(accessor, null, isSetter: false); + + if (PropertyOverriddenNode.CanShow(analyzedProperty)) + yield return new PropertyOverriddenNode(analyzedProperty); + + if (PropertyOverridesNode.CanShow(analyzedProperty)) + yield return new PropertyOverridesNode(analyzedProperty); + + if (InterfacePropertyImplementedByNode.CanShow(analyzedProperty)) + yield return new InterfacePropertyImplementedByNode(analyzedProperty); + } + + public static AnalyzerTreeNodeData? TryCreateAnalyzer(IMemberRef? member, IDecompiler decompiler) { + if (CanShow(member, decompiler)) + return new PropertyNode((PropertyDef)member!); + else + return null; + } + + public static bool CanShow(IMemberRef? member, IDecompiler decompiler) => member is PropertyDef; + public override IMemberRef? Member => analyzedProperty; + public override IMDTokenProvider? Reference => analyzedProperty; + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/PropertyOverriddenNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/PropertyOverriddenNode.cs new file mode 100644 index 0000000..ff1d227 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/PropertyOverriddenNode.cs @@ -0,0 +1,87 @@ +/* + Copyright (C) 2017 HoLLy + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using dnlib.DotNet; +using dnSpy.Analyzer.Properties; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Text; + +namespace dnSpy.Analyzer.TreeNodes { + sealed class PropertyOverriddenNode : SearchNode { + readonly List analyzedTypes; + readonly PropertyDef analyzedProperty; + + public PropertyOverriddenNode(PropertyDef analyzedProperty) { + this.analyzedProperty = analyzedProperty ?? throw new ArgumentNullException(nameof(analyzedProperty)); + analyzedTypes = new List { analyzedProperty.DeclaringType }; + } + + protected override void Write(ITextColorWriter output, IDecompiler decompiler) => + output.Write(BoxedTextColor.Text, dnSpy_Analyzer_Resources.OverridesTreeNode); + + protected override IEnumerable FetchChildren(CancellationToken ct) { + AddTypeEquivalentTypes(Context.DocumentService, analyzedTypes[0], analyzedTypes); + foreach (var declType in analyzedTypes) { + var analyzedAccessor = GetVirtualAccessor(analyzedProperty.GetMethod) ?? GetVirtualAccessor(analyzedProperty.SetMethod); + if (analyzedAccessor?.Overrides is IList overrides && overrides.Count > 0) { + bool matched = false; + foreach (var o in overrides) { + if (o.MethodDeclaration.ResolveMethodDef() is MethodDef method && (method.IsVirtual || method.IsAbstract)) { + if (method.DeclaringType.Properties.FirstOrDefault(a => (object?)a.GetMethod == method || (object?)a.SetMethod == method) is PropertyDef property) { + matched = true; + yield return new PropertyNode(property) { Context = Context }; + } + } + } + if (matched) + yield break; + } + + foreach (var property in TypesHierarchyHelpers.FindBaseProperties(analyzedProperty, declType)) { + var anyAccessor = GetVirtualAccessor(property.GetMethod) ?? GetVirtualAccessor(property.SetMethod); + if (anyAccessor is null || !(anyAccessor.IsVirtual || anyAccessor.IsAbstract)) + continue; + yield return new PropertyNode(property) { Context = Context }; + yield break; + } + } + } + + public static bool CanShow(PropertyDef property) => + (GetAccessor(property.GetMethod) ?? GetAccessor(property.SetMethod)) is not null; + + static MethodDef? GetAccessor(MethodDef? accessor) { + if (accessor is not null && + accessor.DeclaringType.BaseType is not null && + (accessor.IsVirtual || accessor.IsAbstract) && accessor.IsReuseSlot) + return accessor; + return null; + } + + static MethodDef? GetVirtualAccessor(MethodDef? accessor) { + if (accessor is not null && (accessor.IsVirtual || accessor.IsAbstract)) + return accessor; + return null; + } + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/PropertyOverridesNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/PropertyOverridesNode.cs new file mode 100644 index 0000000..f2593eb --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/PropertyOverridesNode.cs @@ -0,0 +1,62 @@ +// 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 System.Collections.Generic; +using System.Threading; +using dnlib.DotNet; +using dnSpy.Analyzer.Properties; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Text; + +namespace dnSpy.Analyzer.TreeNodes { + sealed class PropertyOverridesNode : SearchNode { + readonly PropertyDef analyzedProperty; + + public PropertyOverridesNode(PropertyDef analyzedProperty) => this.analyzedProperty = analyzedProperty ?? throw new ArgumentNullException(nameof(analyzedProperty)); + + protected override void Write(ITextColorWriter output, IDecompiler decompiler) => + output.Write(BoxedTextColor.Text, dnSpy_Analyzer_Resources.OverriddenByTreeNode); + + protected override IEnumerable FetchChildren(CancellationToken ct) { + var analyzer = new ScopedWhereUsedAnalyzer(Context.DocumentService, analyzedProperty, FindReferencesInType); + return analyzer.PerformAnalysis(ct); + } + + IEnumerable FindReferencesInType(TypeDef type) { + if (!TypesHierarchyHelpers.IsBaseType(analyzedProperty.DeclaringType, type, resolveTypeArguments: false)) + yield break; + + foreach (PropertyDef property in type.Properties) { + + if (TypesHierarchyHelpers.IsBaseProperty(analyzedProperty, property)) { + MethodDef anyAccessor = property.GetMethod ?? property.SetMethod; + if (anyAccessor is null) + continue; + bool hidesParent = !anyAccessor.IsVirtual ^ anyAccessor.IsNewSlot; + yield return new PropertyNode(property, hidesParent) { Context = Context }; + } + } + } + + public static bool CanShow(PropertyDef property) { + var accessor = property.GetMethod ?? property.SetMethod; + return accessor is not null && accessor.IsVirtual && !accessor.IsFinal && !accessor.DeclaringType.IsInterface; + } + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/ScopedWhereUsedAnalyzer.cs b/Extensions/dnSpy.Analyzer/TreeNodes/ScopedWhereUsedAnalyzer.cs new file mode 100644 index 0000000..99ec90b --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/ScopedWhereUsedAnalyzer.cs @@ -0,0 +1,341 @@ +// 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 System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using dnlib.DotNet; +using dnSpy.Contracts.Documents; + +namespace dnSpy.Analyzer.TreeNodes { + [Flags] + enum ScopedWhereUsedAnalyzerOptions { + None = 0, + + /// + /// Search all modules, eg. used if it's a type opted in to type equivalence, or DllImport methods, or COM types/members. + /// + IncludeAllModules = 0x00000001, + + /// + /// Force accessibility to public, eg. used by DllImport methods since DllImport methods with + /// the same name and module are considered to be the same method. + /// + ForcePublic = 0x00000002, + } + + /// + /// Determines the accessibility domain of a member for where-used analysis. + /// + sealed class ScopedWhereUsedAnalyzer { + readonly IDsDocumentService documentService; + readonly TypeDef analyzedType; + readonly List allModules; + readonly ScopedWhereUsedAnalyzerOptions options; + TypeDef typeScope; + + internal List AllModules => allModules; + + readonly Accessibility memberAccessibility = Accessibility.Public; + Accessibility typeAccessibility = Accessibility.Public; + readonly Func> typeAnalysisFunction; + + public ScopedWhereUsedAnalyzer(IDsDocumentService documentService, TypeDef analyzedType, Func> typeAnalysisFunction, ScopedWhereUsedAnalyzerOptions options = ScopedWhereUsedAnalyzerOptions.None) { + this.analyzedType = analyzedType; + typeScope = analyzedType; + this.typeAnalysisFunction = typeAnalysisFunction; + this.documentService = documentService; + allModules = new List(); + this.options = options; + } + + public ScopedWhereUsedAnalyzer(IDsDocumentService documentService, MethodDef method, Func> typeAnalysisFunction, ScopedWhereUsedAnalyzerOptions options = ScopedWhereUsedAnalyzerOptions.None) + : this(documentService, method.DeclaringType, typeAnalysisFunction, options) => memberAccessibility = GetMethodAccessibility(method, options); + + public ScopedWhereUsedAnalyzer(IDsDocumentService documentService, PropertyDef property, Func> typeAnalysisFunction, ScopedWhereUsedAnalyzerOptions options = ScopedWhereUsedAnalyzerOptions.None) + : this(documentService, property.DeclaringType, typeAnalysisFunction, options) { + var getterAccessibility = property.GetMethod is null ? Accessibility.Private : GetMethodAccessibility(property.GetMethod, options); + var setterAccessibility = property.SetMethod is null ? Accessibility.Private : GetMethodAccessibility(property.SetMethod, options); + memberAccessibility = (Accessibility)Math.Max((int)getterAccessibility, (int)setterAccessibility); + } + + public ScopedWhereUsedAnalyzer(IDsDocumentService documentService, EventDef eventDef, Func> typeAnalysisFunction, ScopedWhereUsedAnalyzerOptions options = ScopedWhereUsedAnalyzerOptions.None) + : this(documentService, eventDef.DeclaringType, typeAnalysisFunction, options) { + var adderAccessibility = eventDef.AddMethod is null ? Accessibility.Private : GetMethodAccessibility(eventDef.AddMethod, options); + var removerAccessibility = eventDef.RemoveMethod is null ? Accessibility.Private : GetMethodAccessibility(eventDef.RemoveMethod, options); + var invokerAccessibility = eventDef.InvokeMethod is null ? Accessibility.Private : GetMethodAccessibility(eventDef.InvokeMethod, options); + memberAccessibility = (Accessibility)Math.Max(Math.Max((int)adderAccessibility, (int)removerAccessibility), (int)invokerAccessibility); + } + + public ScopedWhereUsedAnalyzer(IDsDocumentService documentService, FieldDef field, Func> typeAnalysisFunction, ScopedWhereUsedAnalyzerOptions options = ScopedWhereUsedAnalyzerOptions.None) + : this(documentService, field.DeclaringType, typeAnalysisFunction, options) { + switch ((options & ScopedWhereUsedAnalyzerOptions.ForcePublic) != 0 ? FieldAttributes.Public : 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.PrivateScope: + case FieldAttributes.Family: + memberAccessibility = Accessibility.Family; + break; + case FieldAttributes.FamORAssem: + memberAccessibility = Accessibility.FamilyOrInternal; + break; + case FieldAttributes.Public: + memberAccessibility = Accessibility.Public; + break; + } + } + + Accessibility GetMethodAccessibility(MethodDef method, ScopedWhereUsedAnalyzerOptions options) { + if (method is null) + return 0; + if ((options & ScopedWhereUsedAnalyzerOptions.ForcePublic) != 0) + return Accessibility.Public; + Accessibility accessibility; + switch (method.Attributes & MethodAttributes.MemberAccessMask) { + case MethodAttributes.Private: + default: + accessibility = Accessibility.Private; + break; + case MethodAttributes.FamANDAssem: + accessibility = Accessibility.FamilyAndInternal; + break; + case MethodAttributes.PrivateScope: + case MethodAttributes.Family: + accessibility = Accessibility.Family; + break; + case MethodAttributes.Assembly: + accessibility = Accessibility.Internal; + break; + case MethodAttributes.FamORAssem: + accessibility = Accessibility.FamilyOrInternal; + break; + case MethodAttributes.Public: + accessibility = Accessibility.Public; + break; + } + return accessibility; + } + + public IEnumerable PerformAnalysis(CancellationToken ct) { + if (memberAccessibility == Accessibility.Private) { + return FindReferencesInTypeScope(ct); + } + + DetermineTypeAccessibility(); + + if (typeAccessibility == Accessibility.Private) { + return FindReferencesInEnclosingTypeScope(ct); + } + + if (memberAccessibility == Accessibility.Internal || + memberAccessibility == Accessibility.FamilyAndInternal || + typeAccessibility == Accessibility.Internal || + typeAccessibility == Accessibility.FamilyAndInternal) + return FindReferencesInAssemblyAndFriends(ct); + + return FindReferencesGlobal(ct); + } + + 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 (GetTypeAccessibility(typeScope) == Accessibility.Internal && + ((int)typeAccessibility > (int)Accessibility.Internal)) { + typeAccessibility = Accessibility.Internal; + } + } + + Accessibility GetTypeAccessibility(TypeDef type) { + if ((options & ScopedWhereUsedAnalyzerOptions.ForcePublic) != 0) + return Accessibility.Public; + return type.IsNotPublic ? Accessibility.Internal : Accessibility.Public; + } + + Accessibility GetNestedTypeAccessibility(TypeDef type) { + if ((options & ScopedWhereUsedAnalyzerOptions.ForcePublic) != 0) + return Accessibility.Public; + 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 + /// + enum Accessibility { + Private, + FamilyAndInternal, + Internal, + Family, + FamilyOrInternal, + Public + } + + IEnumerable FindReferencesInAssemblyAndFriends(CancellationToken ct) { + IEnumerable modules; + if ((options & ScopedWhereUsedAnalyzerOptions.IncludeAllModules) != 0) + modules = documentService.GetDocuments().Select(a => a.ModuleDef).OfType(); + else if (TIAHelper.IsTypeDefEquivalent(analyzedType)) { + var analyzedTypes = new List { analyzedType }; + SearchNode.AddTypeEquivalentTypes(documentService, analyzedType, analyzedTypes); + modules = SearchNode.GetTypeEquivalentModules(analyzedTypes); + } + else + modules = GetModuleAndAnyFriends(analyzedType.Module, ct); + allModules.AddRange(modules); + return allModules.AsParallel().WithCancellation(ct).SelectMany(a => FindReferencesInModule(a, ct)); + } + + IEnumerable FindReferencesGlobal(CancellationToken ct) { + IEnumerable modules; + if ((options & ScopedWhereUsedAnalyzerOptions.IncludeAllModules) != 0) + modules = documentService.GetDocuments().Select(a => a.ModuleDef).OfType(); + else if (TIAHelper.IsTypeDefEquivalent(analyzedType)) { + var analyzedTypes = new List { analyzedType }; + SearchNode.AddTypeEquivalentTypes(documentService, analyzedType, analyzedTypes); + modules = SearchNode.GetTypeEquivalentModules(analyzedTypes); + } + else + modules = GetReferencingModules(analyzedType.Module, ct); + allModules.AddRange(modules); + return allModules.AsParallel().WithCancellation(ct).SelectMany(a => FindReferencesInModule(a, ct)); + } + + IEnumerable FindReferencesInModule(ModuleDef mod, CancellationToken ct) { + foreach (TypeDef type in TreeTraversal.PreOrder(mod.Types, t => t.NestedTypes)) { + ct.ThrowIfCancellationRequested(); + foreach (var result in typeAnalysisFunction(type)) { + ct.ThrowIfCancellationRequested(); + yield return result; + } + } + } + + IEnumerable FindReferencesInTypeScope(CancellationToken ct) { + foreach (TypeDef type in TreeTraversal.PreOrder(typeScope, t => t.NestedTypes)) { + ct.ThrowIfCancellationRequested(); + foreach (var result in typeAnalysisFunction(type)) { + ct.ThrowIfCancellationRequested(); + yield return result; + } + } + } + + IEnumerable FindReferencesInEnclosingTypeScope(CancellationToken ct) { + foreach (TypeDef type in TreeTraversal.PreOrder(typeScope.DeclaringType, t => t.NestedTypes)) { + ct.ThrowIfCancellationRequested(); + foreach (var result in typeAnalysisFunction(type)) { + ct.ThrowIfCancellationRequested(); + yield return result; + } + } + } + + IEnumerable GetReferencingModules(ModuleDef mod, CancellationToken ct) { + var asm = mod.Assembly; + if (asm is null) { + yield return mod; + yield break; + } + foreach (var m in mod.Assembly.Modules) + yield return m; + + var modules = documentService.GetDocuments().Where(a => SearchNode.CanIncludeModule(mod, a.ModuleDef)); + + foreach (var module in modules) { + Debug2.Assert(module.ModuleDef is not null); + ct.ThrowIfCancellationRequested(); + if (ModuleReferencesScopeType(module.ModuleDef)) + yield return module.ModuleDef; + } + } + + IEnumerable GetModuleAndAnyFriends(ModuleDef mod, CancellationToken ct) { + var asm = mod.Assembly; + if (asm is null) { + yield return mod; + yield break; + } + foreach (var m in mod.Assembly.Modules) + yield return m; + + var friendAssemblies = SearchNode.GetFriendAssemblies(documentService, mod, out var modules); + if (friendAssemblies.Count > 0) { + foreach (var module in modules) { + Debug2.Assert(module.ModuleDef is not null); + ct.ThrowIfCancellationRequested(); + if ((module.AssemblyDef is null || friendAssemblies.Contains(module.AssemblyDef.Name)) && ModuleReferencesScopeType(module.ModuleDef)) + yield return module.ModuleDef; + } + } + } + + bool ModuleReferencesScopeType(ModuleDef mod) { + foreach (var typeRef in mod.GetTypeRefs()) { + if (new SigComparer().Equals(typeScope, typeRef)) + return true; + } + foreach (var exportedType in mod.ExportedTypes) { + if (!exportedType.MovedToAnotherAssembly) + continue; + if (new SigComparer().Equals(typeScope, exportedType)) + return true; + } + return false; + } + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/SearchNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/SearchNode.cs new file mode 100644 index 0000000..279b7a0 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/SearchNode.cs @@ -0,0 +1,182 @@ +// 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 System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using dnlib.DotNet; +using dnSpy.Contracts.Documents; +using dnSpy.Contracts.Images; +using dnSpy.Contracts.TreeView; + +namespace dnSpy.Analyzer.TreeNodes { + /// + /// Base class for analyzer nodes that perform a search. + /// + abstract class SearchNode : AnalyzerTreeNodeData, IAsyncCancellable { + protected SearchNode() { + } + + public override void Initialize() => TreeNode.LazyLoading = true; + protected override ImageReference GetIcon(IDotNetImageService dnImgMgr) => DsImages.Search; + + public override IEnumerable CreateChildren() { + Debug2.Assert(asyncFetchChildrenHelper is null); + asyncFetchChildrenHelper = new AsyncFetchChildrenHelper(this, () => asyncFetchChildrenHelper = null); + yield break; + } + AsyncFetchChildrenHelper? asyncFetchChildrenHelper; + + protected abstract IEnumerable FetchChildren(CancellationToken ct); + internal IEnumerable FetchChildrenInternal(CancellationToken token) => FetchChildren(token); + + public override void OnIsVisibleChanged() { + if (!TreeNode.IsVisible && asyncFetchChildrenHelper is not null && !asyncFetchChildrenHelper.CompletedSuccessfully) { + CancelAndClearChildren(); + TreeNode.LazyLoading = true; + } + } + + public override void OnIsExpandedChanged(bool isExpanded) { + if (!isExpanded && asyncFetchChildrenHelper is not null && !asyncFetchChildrenHelper.CompletedSuccessfully) { + CancelAndClearChildren(); + TreeNode.LazyLoading = true; + } + } + + public override bool HandleAssemblyListChanged(IDsDocument[] removedAssemblies, IDsDocument[] addedAssemblies) { + // only cancel a running analysis if user has manually added/removed assemblies + bool manualAdd = false; + foreach (var asm in addedAssemblies) { + if (!asm.IsAutoLoaded) + manualAdd = true; + } + if (removedAssemblies.Length > 0 || manualAdd) { + CancelAndClearChildren(); + } + return true; + } + + public override bool HandleModelUpdated(IDsDocument[] documents) { + CancelAndClearChildren(); + return true; + } + + void CancelAndClearChildren() { + AnalyzerTreeNodeData.CancelSelfAndChildren(this); + TreeNode.Children.Clear(); + TreeNode.LazyLoading = true; + } + + public void Cancel() { + asyncFetchChildrenHelper?.Cancel(); + asyncFetchChildrenHelper = null; + } + + internal static bool CanIncludeModule(ModuleDef targetModule, ModuleDef? module) { + if (module is null) + return false; + if (targetModule == module) + return false; + if (targetModule.Assembly is not null && targetModule.Assembly == module.Assembly) + return false; + return true; + } + + internal static HashSet GetFriendAssemblies(IDsDocumentService documentService, ModuleDef mod, out IDsDocument[] modules) { + var asm = mod.Assembly; + Debug2.Assert(asm is not null); + var friendAssemblies = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var attribute in asm.CustomAttributes.FindAll("System.Runtime.CompilerServices.InternalsVisibleToAttribute")) { + if (attribute.ConstructorArguments.Count == 0) + continue; + string assemblyName = attribute.ConstructorArguments[0].Value as UTF8String; + if (assemblyName is null) + continue; + assemblyName = new AssemblyNameInfo(assemblyName).Name; + friendAssemblies.Add(assemblyName); + } + modules = documentService.GetDocuments().Where(a => CanIncludeModule(mod, a.ModuleDef)).ToArray(); + foreach (var module in modules) { + Debug2.Assert(module.ModuleDef is not null); + var asm2 = module.AssemblyDef; + if (asm2 is null) + continue; + foreach (var attribute in asm2.CustomAttributes.FindAll("System.Runtime.CompilerServices.IgnoresAccessChecksToAttribute")) { + string assemblyName = attribute.ConstructorArguments[0].Value as UTF8String; + if (assemblyName is null) + continue; + assemblyName = new AssemblyNameInfo(assemblyName).Name; + if (StringComparer.OrdinalIgnoreCase.Equals(asm.Name.String, assemblyName)) + friendAssemblies.Add(asm2.Name); + } + } + return friendAssemblies; + } + + internal static void AddTypeEquivalentTypes(IDsDocumentService documentService, TypeDef analyzedType, List analyzedTypes) { + Debug.Assert(analyzedTypes.Count == 1 && analyzedTypes[0] == analyzedType); + if (!TIAHelper.IsTypeDefEquivalent(analyzedType)) + return; + foreach (var document in documentService.GetDocuments().Where(a => a.ModuleDef is not null)) { + foreach (var type in GetTypeEquivalentTypes(document.AssemblyDef, document.ModuleDef, analyzedType)) { + if (type != analyzedType) + analyzedTypes.Add(type); + } + } + } + + static IEnumerable GetTypeEquivalentTypes(AssemblyDef? assembly, ModuleDef? module, TypeDef type) { + Debug.Assert(TIAHelper.IsTypeDefEquivalent(type)); + var typeRef = new ModuleDefUser("dummy").Import(type); + foreach (var mod in GetModules(assembly, module)) { + var otherType = mod.Find(typeRef); + if (otherType != type && TIAHelper.IsTypeDefEquivalent(otherType) && + new SigComparer().Equals(otherType, type) && + !new SigComparer(SigComparerOptions.DontCheckTypeEquivalence).Equals(otherType, type)) { + yield return otherType; + } + } + } + + static IEnumerable GetModules(AssemblyDef? assembly, ModuleDef? module) { + if (assembly is not null) { + foreach (var mod in assembly.Modules) + yield return mod; + } + else { + if (module is not null) + yield return module; + } + } + + protected static IEnumerable<(ModuleDef module, ITypeDefOrRef type)> GetTypeEquivalentModulesAndTypes(List analyzedTypes) { + foreach (var type in analyzedTypes) + yield return (type.Module, type); + } + + internal static IEnumerable GetTypeEquivalentModules(List analyzedTypes) { + foreach (var type in analyzedTypes) + yield return type.Module; + } + + protected static bool CheckEquals(IMemberRef? mr1, IMemberRef? mr2) => Helpers.CheckEquals(mr1, mr2); + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/SourceRef.cs b/Extensions/dnSpy.Analyzer/TreeNodes/SourceRef.cs new file mode 100644 index 0000000..75aa8fa --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/SourceRef.cs @@ -0,0 +1,33 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using dnlib.DotNet; + +namespace dnSpy.Analyzer.TreeNodes { + readonly struct SourceRef { + public MethodDef Method { get; } + public uint? ILOffset { get; } + public IMDTokenProvider? Reference { get; } + public SourceRef(MethodDef method, uint? offset, IMDTokenProvider? reference) { + Method = method; + ILOffset = offset; + Reference = reference; + } + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/TIAHelper.cs b/Extensions/dnSpy.Analyzer/TreeNodes/TIAHelper.cs new file mode 100644 index 0000000..6f51083 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/TIAHelper.cs @@ -0,0 +1,221 @@ +// dnlib: See LICENSE.txt for more info + +// See coreclr/src/vm/siginfo.cpp + +using System; +using System.Diagnostics; + +namespace dnlib.DotNet { + /// + /// System.Runtime.InteropServices.TypeIdentifierAttribute helper code used by + /// + static class TIAHelper { + readonly struct Info : IEquatable { + public readonly UTF8String? Scope; + public readonly UTF8String? Identifier; + + public Info(UTF8String? scope, UTF8String? identifier) { + Scope = scope; + Identifier = identifier; + } + + public bool Equals(Info other) => stricmp(Scope, other.Scope) && UTF8String.Equals(Identifier, other.Identifier); + + static bool stricmp(UTF8String? a, UTF8String? b) { + var da = a?.Data; + var db = b?.Data; + if (da == db) + return true; + if (da == null || db == null) + return false; + if (da.Length != db.Length) + return false; + for (int i = 0; i < da.Length; i++) { + byte ba = da[i], bb = db[i]; + if ((byte)'A' <= ba && ba <= (byte)'Z') + ba = (byte)(ba - 'A' + 'a'); + if ((byte)'A' <= bb && bb <= (byte)'Z') + bb = (byte)(bb - 'A' + 'a'); + if (ba != bb) + return false; + } + return true; + } + } + + static Info? GetInfo(TypeDef td) { + if (td == null) + return null; + if (td.IsWindowsRuntime) + return null; + + UTF8String? scope = null, identifier = null; + var tia = td.CustomAttributes.Find("System.Runtime.InteropServices.TypeIdentifierAttribute"); + if (tia != null) { + if (tia.ConstructorArguments.Count >= 2) { + if (tia.ConstructorArguments[0].Type.GetElementType() != ElementType.String) + return null; + if (tia.ConstructorArguments[1].Type.GetElementType() != ElementType.String) + return null; + scope = tia.ConstructorArguments[0].Value as UTF8String ?? tia.ConstructorArguments[0].Value as string; + identifier = tia.ConstructorArguments[1].Value as UTF8String ?? tia.ConstructorArguments[1].Value as string; + } + } + else { + var asm = td.Module?.Assembly; + if (asm == null) + return null; + bool isTypeLib = asm.CustomAttributes.IsDefined("System.Runtime.InteropServices.ImportedFromTypeLibAttribute") || + asm.CustomAttributes.IsDefined("System.Runtime.InteropServices.PrimaryInteropAssemblyAttribute"); + if (!isTypeLib) + return null; + } + + if (UTF8String.IsNull(identifier)) { + CustomAttribute gca; + if (td.IsInterface && td.IsImport) + gca = td.CustomAttributes.Find("System.Runtime.InteropServices.GuidAttribute"); + else { + var asm = td.Module?.Assembly; + if (asm == null) + return null; + gca = asm.CustomAttributes.Find("System.Runtime.InteropServices.GuidAttribute"); + } + if (gca == null) + return null; + if (gca.ConstructorArguments.Count < 1) + return null; + if (gca.ConstructorArguments[0].Type.GetElementType() != ElementType.String) + return null; + scope = gca.ConstructorArguments[0].Value as UTF8String ?? gca.ConstructorArguments[0].Value as string; + var ns = td.Namespace; + var name = td.Name; + if (UTF8String.IsNullOrEmpty(ns)) + identifier = name; + else if (UTF8String.IsNullOrEmpty(name)) + identifier = new UTF8String(Concat(ns.Data, (byte)'.', Array.Empty())); + else + identifier = new UTF8String(Concat(ns.Data, (byte)'.', name.Data)); + } + return new Info(scope, identifier); + } + + static byte[] Concat(byte[] a, byte b, byte[] c) { + var data = new byte[a.Length + 1 + c.Length]; + for (int i = 0; i < a.Length; i++) + data[i] = a[i]; + data[a.Length] = b; + for (int i = 0, j = a.Length + 1; i < c.Length; i++, j++) + data[j] = c[i]; + return data; + } + + internal static bool IsTypeDefEquivalent(TypeDef td) => GetInfo(td) != null && CheckEquivalent(td); + + static bool CheckEquivalent(TypeDef td) { + Debug2.Assert(td != null); + + for (int i = 0; td != null && i < 1000; i++) { + if (i != 0) { + var info = GetInfo(td); + if (info == null) + return false; + } + + bool f; + if (td.IsInterface) + f = td.IsImport || td.CustomAttributes.IsDefined("System.Runtime.InteropServices.ComEventInterfaceAttribute"); + else + f = td.IsValueType || td.IsDelegate; + if (!f) + return false; + if (td.GenericParameters.Count > 0) + return false; + + var declType = td.DeclaringType; + if (declType == null) + return td.IsPublic; + + if (!td.IsNestedPublic) + return false; + td = declType; + } + + return false; + } + + public static bool Equivalent(TypeDef td1, TypeDef td2) { + var info1 = GetInfo(td1); + if (info1 == null) + return false; + var info2 = GetInfo(td2); + if (info2 == null) + return false; + if (!CheckEquivalent(td1) || !CheckEquivalent(td2)) + return false; + if (!info1.Value.Equals(info2.Value)) + return false; + + // Caller has already compared names of the types and any declaring types + + for (int i = 0; i < 1000; i++) { + if (td1.IsInterface) { + if (!td2.IsInterface) + return false; + } + else { + var bt1 = td1.BaseType; + var bt2 = td2.BaseType; + if (bt1 == null || bt2 == null) + return false; + if (td1.IsDelegate) { + if (!td2.IsDelegate) + return false; + if (!DelegateEquals(td1, td2)) + return false; + } + else if (td1.IsValueType) { + if (td1.IsEnum != td2.IsEnum) + return false; + if (!td2.IsValueType) + return false; + if (!ValueTypeEquals(td1, td2, td1.IsEnum)) + return false; + } + else + return false; + } + + td1 = td1.DeclaringType; + td2 = td2.DeclaringType; + if (td1 == null && td2 == null) + break; + if (td1 == null || td2 == null) + return false; + } + + return true; + } + + static bool DelegateEquals(TypeDef td1, TypeDef td2) { + var invoke1 = td1.FindMethod(InvokeString); + var invoke2 = td2.FindMethod(InvokeString); + if (invoke1 == null || invoke2 == null) + return false; + + //TODO: Compare method signatures. Prevent infinite recursion... + + return true; + } + static readonly UTF8String InvokeString = new UTF8String("Invoke"); + + static bool ValueTypeEquals(TypeDef td1, TypeDef td2, bool isEnum) { + if (td1.Methods.Count != 0 || td2.Methods.Count != 0) + return false; + + //TODO: Compare the fields. Prevent infinite recursion... + + return true; + } + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/TypeExposedByNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/TypeExposedByNode.cs new file mode 100644 index 0000000..5705918 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/TypeExposedByNode.cs @@ -0,0 +1,233 @@ +// 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 System.Collections.Generic; +using System.Threading; +using dnlib.DotNet; +using dnSpy.Analyzer.Properties; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Text; + +namespace dnSpy.Analyzer.TreeNodes { + sealed class TypeExposedByNode : SearchNode { + readonly TypeDef analyzedType; + Guid comGuid; + bool isComType; + + public TypeExposedByNode(TypeDef analyzedType) => this.analyzedType = analyzedType ?? throw new ArgumentNullException(nameof(analyzedType)); + + protected override void Write(ITextColorWriter output, IDecompiler decompiler) => + output.Write(BoxedTextColor.Text, dnSpy_Analyzer_Resources.ExposedByTreeNode); + + protected override IEnumerable FetchChildren(CancellationToken ct) { + bool includeAllModules; + isComType = ComUtils.IsComType(analyzedType, out comGuid); + includeAllModules = isComType; + var options = ScopedWhereUsedAnalyzerOptions.None; + if (includeAllModules) + options |= ScopedWhereUsedAnalyzerOptions.IncludeAllModules; + if (isComType) + options |= ScopedWhereUsedAnalyzerOptions.ForcePublic; + var analyzer = new ScopedWhereUsedAnalyzer(Context.DocumentService, analyzedType, FindReferencesInType, options); + return analyzer.PerformAnalysis(ct); + } + + IEnumerable FindReferencesInType(TypeDef type) { + if (analyzedType.IsEnum && new SigComparer().Equals(type, analyzedType)) + yield break; + + foreach (FieldDef field in type.Fields) { + if (TypeIsExposedBy(field)) { + yield return new FieldNode(field) { Context = Context }; + } + } + + foreach (PropertyDef property in type.Properties) { + if (TypeIsExposedBy(property)) { + yield return new PropertyNode(property) { Context = Context }; + } + } + + foreach (EventDef eventDef in type.Events) { + if (TypeIsExposedBy(eventDef)) { + yield return new EventNode(eventDef) { Context = Context }; + } + } + + foreach (MethodDef method in type.Methods) { + if (TypeIsExposedBy(method)) { + yield return new MethodNode(method) { Context = Context }; + } + } + } + + bool CheckType(IType? type) => CheckType(type, 0); + + const int maxRecursion = 20; + bool CheckType(IType? type, int recursionCounter) { + if (recursionCounter > maxRecursion) + return false; + if (type is TypeSig ts) + return CheckType(ts, recursionCounter + 1); + if (type is TypeSpec typeSpec) + return CheckType(typeSpec.TypeSig, recursionCounter + 1); + if (isComType && type.Resolve() is TypeDef td && ComUtils.ComEquals(td, ref comGuid)) + return true; + return new SigComparer().Equals(analyzedType, type); + } + + bool CheckType(TypeSig? sig, int recursionCounter) { + if (recursionCounter > maxRecursion) + return false; + if (sig is null) + return false; + switch (sig.ElementType) { + case ElementType.Void: return new SigComparer().Equals(analyzedType, analyzedType.Module.CorLibTypes.Void); + case ElementType.Boolean: return new SigComparer().Equals(analyzedType, analyzedType.Module.CorLibTypes.Boolean); + case ElementType.Char: return new SigComparer().Equals(analyzedType, analyzedType.Module.CorLibTypes.Char); + case ElementType.I1: return new SigComparer().Equals(analyzedType, analyzedType.Module.CorLibTypes.SByte); + case ElementType.U1: return new SigComparer().Equals(analyzedType, analyzedType.Module.CorLibTypes.Byte); + case ElementType.I2: return new SigComparer().Equals(analyzedType, analyzedType.Module.CorLibTypes.Int16); + case ElementType.U2: return new SigComparer().Equals(analyzedType, analyzedType.Module.CorLibTypes.UInt16); + case ElementType.I4: return new SigComparer().Equals(analyzedType, analyzedType.Module.CorLibTypes.Int32); + case ElementType.U4: return new SigComparer().Equals(analyzedType, analyzedType.Module.CorLibTypes.UInt32); + case ElementType.I8: return new SigComparer().Equals(analyzedType, analyzedType.Module.CorLibTypes.Int64); + case ElementType.U8: return new SigComparer().Equals(analyzedType, analyzedType.Module.CorLibTypes.UInt64); + case ElementType.R4: return new SigComparer().Equals(analyzedType, analyzedType.Module.CorLibTypes.Single); + case ElementType.R8: return new SigComparer().Equals(analyzedType, analyzedType.Module.CorLibTypes.Double); + case ElementType.String: return new SigComparer().Equals(analyzedType, analyzedType.Module.CorLibTypes.String); + case ElementType.TypedByRef:return new SigComparer().Equals(analyzedType, analyzedType.Module.CorLibTypes.TypedReference); + case ElementType.I: return new SigComparer().Equals(analyzedType, analyzedType.Module.CorLibTypes.IntPtr); + case ElementType.U: return new SigComparer().Equals(analyzedType, analyzedType.Module.CorLibTypes.UIntPtr); + case ElementType.Object: return new SigComparer().Equals(analyzedType, analyzedType.Module.CorLibTypes.Object); + + case ElementType.Ptr: + case ElementType.ByRef: + case ElementType.Array: + case ElementType.SZArray: + case ElementType.ValueArray: + case ElementType.Module: + case ElementType.Pinned: + return CheckType(sig.Next, recursionCounter + 1); + + case ElementType.CModReqd: + case ElementType.CModOpt: + return CheckType(((ModifierSig)sig).Modifier, recursionCounter + 1) || CheckType(sig.Next, recursionCounter + 1); + + case ElementType.ValueType: + case ElementType.Class: + return CheckType(((TypeDefOrRefSig)sig).TypeDefOrRef, recursionCounter + 1); + + case ElementType.GenericInst: + if (CheckType(((GenericInstSig)sig).GenericType?.TypeDefOrRef, recursionCounter + 1)) + return true; + foreach (var genericArg in ((GenericInstSig)sig).GenericArguments) { + if (CheckType(genericArg, recursionCounter + 1)) + return true; + } + return false; + + case ElementType.FnPtr: + return TypeIsExposedBy(((FnPtrSig)sig).MethodSig, recursionCounter + 1); + + case ElementType.End: + case ElementType.Var: + case ElementType.R: + case ElementType.MVar: + case ElementType.Internal: + case ElementType.Sentinel: + default: + return false; + } + } + + bool TypeIsExposedBy(FieldDef field) { + if (field.IsPrivate) + return false; + + return CheckType(field.FieldType); + } + + bool TypeIsExposedBy(PropertyDef property) { + if (IsPrivate(property)) + return false; + + return TypeIsExposedBy(property.PropertySig); + } + + bool TypeIsExposedBy(EventDef eventDef) { + if (IsPrivate(eventDef)) + return false; + + return CheckType(eventDef.EventType); + } + + bool TypeIsExposedBy(MethodDef method) { + // if the method has overrides, it is probably an explicit interface member + // and should be considered part of the public API even though it is marked private. + if (method.IsPrivate) { + if (!method.HasOverrides) + return false; + var methDecl = method.Overrides[0].MethodDeclaration; + var typeDef = methDecl?.DeclaringType?.ResolveTypeDef(); + if (typeDef is not null && !typeDef.IsInterface) + return false; + } + + // exclude methods with 'semantics'. for example, property getters & setters. + // HACK: this is a potentially fragile implementation, as the MethodSemantics may be extended to other uses at a later date. + if (method.SemanticsAttributes != MethodSemanticsAttributes.None) + return false; + + return TypeIsExposedBy(method.MethodSig); + } + + bool TypeIsExposedBy(MethodBaseSig? methodSig) => TypeIsExposedBy(methodSig, 0); + + bool TypeIsExposedBy(MethodBaseSig? methodSig, int recursionCounter) { + if (recursionCounter > maxRecursion) + return false; + if (methodSig is null) + return false; + + if (CheckType(methodSig.RetType)) + return true; + foreach (var type in methodSig.Params) { + if (CheckType(type)) + return true; + } + return false; + } + + static bool IsPrivate(PropertyDef property) { + bool isGetterPublic = (property.GetMethod is not null && !property.GetMethod.IsPrivate); + bool isSetterPublic = (property.SetMethod is not null && !property.SetMethod.IsPrivate); + return !(isGetterPublic || isSetterPublic); + } + + static bool IsPrivate(EventDef eventDef) { + bool isAdderPublic = (eventDef.AddMethod is not null && !eventDef.AddMethod.IsPrivate); + bool isRemoverPublic = (eventDef.RemoveMethod is not null && !eventDef.RemoveMethod.IsPrivate); + bool isInvokerPublic = (eventDef.InvokeMethod is not null && !eventDef.InvokeMethod.IsPrivate); + return !(isAdderPublic || isRemoverPublic || isInvokerPublic); + } + + public static bool CanShow(TypeDef type) => !(type.IsAbstract && type.IsSealed); + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/TypeExtensionMethodsNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/TypeExtensionMethodsNode.cs new file mode 100644 index 0000000..0778945 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/TypeExtensionMethodsNode.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 System.Collections.Generic; +using System.Threading; +using dnlib.DotNet; +using dnSpy.Analyzer.Properties; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Text; + +namespace dnSpy.Analyzer.TreeNodes { + sealed class TypeExtensionMethodsNode : SearchNode { + readonly TypeDef analyzedType; + Guid comGuid; + bool isComType; + + public TypeExtensionMethodsNode(TypeDef analyzedType) => this.analyzedType = analyzedType ?? throw new ArgumentNullException(nameof(analyzedType)); + + protected override void Write(ITextColorWriter output, IDecompiler decompiler) => + output.Write(BoxedTextColor.Text, dnSpy_Analyzer_Resources.ExtensionMethodsTreeNode); + + protected override IEnumerable FetchChildren(CancellationToken ct) { + bool includeAllModules; + isComType = ComUtils.IsComType(analyzedType, out comGuid); + includeAllModules = isComType; + var options = ScopedWhereUsedAnalyzerOptions.None; + if (includeAllModules) + options |= ScopedWhereUsedAnalyzerOptions.IncludeAllModules; + if (isComType) + options |= ScopedWhereUsedAnalyzerOptions.ForcePublic; + var analyzer = new ScopedWhereUsedAnalyzer(Context.DocumentService, analyzedType, FindReferencesInType, options); + return analyzer.PerformAnalysis(ct); + } + + IEnumerable FindReferencesInType(TypeDef type) { + if (!HasExtensionAttribute(type)) + yield break; + foreach (MethodDef method in type.Methods) { + if (method.IsStatic && HasExtensionAttribute(method)) { + int skip = GetParametersSkip(method.Parameters); + if (method.Parameters.Count <= skip) + continue; + var paramType = method.Parameters[skip].Type?.GetScopeType(); + if (new SigComparer().Equals(analyzedType, paramType) || + (isComType && paramType.Resolve() is TypeDef td && ComUtils.ComEquals(td, ref comGuid))) { + yield return new MethodNode(method) { Context = Context }; + } + } + } + } + + static int GetParametersSkip(IList parameters) { + if (parameters is null || parameters.Count == 0) + return 0; + if (parameters[0].IsHiddenThisParameter) + return 1; + return 0; + } + + bool HasExtensionAttribute(IHasCustomAttribute p) => p.CustomAttributes.Find("System.Runtime.CompilerServices.ExtensionAttribute") is not null; + + // show on all types except static classes + public static bool CanShow(TypeDef type) => !(type.IsAbstract && type.IsSealed); + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/TypeInstantiationsNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/TypeInstantiationsNode.cs new file mode 100644 index 0000000..f43fb2b --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/TypeInstantiationsNode.cs @@ -0,0 +1,74 @@ +// 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 System.Collections.Generic; +using System.Threading; +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using dnSpy.Analyzer.Properties; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Text; + +namespace dnSpy.Analyzer.TreeNodes { + sealed class TypeInstantiationsNode : SearchNode { + readonly TypeDef analyzedType; + readonly bool isSystemObject; + + public TypeInstantiationsNode(TypeDef analyzedType) { + this.analyzedType = analyzedType ?? throw new ArgumentNullException(nameof(analyzedType)); + isSystemObject = analyzedType.DefinitionAssembly.IsCorLib() && analyzedType.FullName == "System.Object"; + } + + protected override void Write(ITextColorWriter output, IDecompiler decompiler) => + output.Write(BoxedTextColor.Text, dnSpy_Analyzer_Resources.InstantiatedByTreeNode); + + protected override IEnumerable FetchChildren(CancellationToken ct) { + var analyzer = new ScopedWhereUsedAnalyzer(Context.DocumentService, analyzedType, FindReferencesInType); + return analyzer.PerformAnalysis(ct); + } + + IEnumerable FindReferencesInType(TypeDef type) { + foreach (MethodDef method in type.Methods) { + if (!method.HasBody) + continue; + + // ignore chained constructors + // (since object is the root of everything, we can short circuit the test in this case) + if (method.Name == ".ctor" && + (isSystemObject || new SigComparer().Equals(analyzedType, type) || TypesHierarchyHelpers.IsBaseType(analyzedType, type, false))) + continue; + + Instruction? foundInstr = null; + foreach (Instruction instr in method.Body.Instructions) { + if (instr.Operand is IMethod mr && !mr.IsField && mr.Name == ".ctor") { + if (Helpers.IsReferencedBy(analyzedType, mr.DeclaringType)) { + foundInstr = instr; + break; + } + } + } + + if (foundInstr is not null) + yield return new MethodNode(method) { Context = Context, SourceRef = new SourceRef(method, foundInstr.Offset, foundInstr.Operand as IMDTokenProvider) }; + } + } + + public static bool CanShow(TypeDef type) => type.IsClass && !type.IsAbstract && !type.IsEnum; + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/TypeNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/TypeNode.cs new file mode 100644 index 0000000..51d0f96 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/TypeNode.cs @@ -0,0 +1,59 @@ +// 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 System.Collections.Generic; +using dnlib.DotNet; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Documents.TreeView; +using dnSpy.Contracts.Images; +using dnSpy.Contracts.Text; +using dnSpy.Contracts.TreeView; + +namespace dnSpy.Analyzer.TreeNodes { + sealed class TypeNode : EntityNode { + readonly TypeDef analyzedType; + + public TypeNode(TypeDef analyzedType) => this.analyzedType = analyzedType ?? throw new ArgumentNullException(nameof(analyzedType)); + + public override void Initialize() => TreeNode.LazyLoading = true; + protected override ImageReference GetIcon(IDotNetImageService dnImgMgr) => dnImgMgr.GetImageReference(analyzedType); + protected override void Write(ITextColorWriter output, IDecompiler decompiler) => + new NodeFormatter().Write(output, decompiler, analyzedType, Context.ShowToken); + + public override IEnumerable CreateChildren() { + if (AttributeAppliedToNode.CanShow(analyzedType)) + yield return new AttributeAppliedToNode(analyzedType); + + if (TypeInstantiationsNode.CanShow(analyzedType)) + yield return new TypeInstantiationsNode(analyzedType); + + if (TypeUsedByNode.CanShow(analyzedType)) + yield return new TypeUsedByNode(analyzedType); + + if (TypeExposedByNode.CanShow(analyzedType)) + yield return new TypeExposedByNode(analyzedType); + + if (TypeExtensionMethodsNode.CanShow(analyzedType)) + yield return new TypeExtensionMethodsNode(analyzedType); + } + + public override IMemberRef? Member => analyzedType; + public override IMDTokenProvider? Reference => analyzedType; + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/TypeUsedByNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/TypeUsedByNode.cs new file mode 100644 index 0000000..4bb5b0c --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/TypeUsedByNode.cs @@ -0,0 +1,361 @@ +// 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 System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using dnlib.DotNet; +using dnSpy.Analyzer.Properties; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Text; + +namespace dnSpy.Analyzer.TreeNodes { + sealed class TypeUsedByNode : SearchNode { + readonly TypeDef analyzedType; + Guid comGuid; + bool isComType; + HashSet? allTypes; + + public TypeUsedByNode(TypeDef analyzedType) => + this.analyzedType = analyzedType ?? throw new ArgumentNullException(nameof(analyzedType)); + + protected override void Write(ITextColorWriter output, IDecompiler decompiler) => + output.Write(BoxedTextColor.Text, dnSpy_Analyzer_Resources.UsedByTreeNode); + + protected override IEnumerable FetchChildren(CancellationToken ct) { + allTypes = new HashSet(); + allTypes.Add(analyzedType); + + bool includeAllModules = CustomAttributesUtils.IsPseudoCustomAttributeType(analyzedType) || + CustomAttributesUtils.IsPseudoCustomAttributeOtherType(analyzedType); + isComType = ComUtils.IsComType(analyzedType, out comGuid); + includeAllModules |= isComType; + var options = ScopedWhereUsedAnalyzerOptions.None; + if (includeAllModules) + options |= ScopedWhereUsedAnalyzerOptions.IncludeAllModules; + if (isComType) + options |= ScopedWhereUsedAnalyzerOptions.ForcePublic; + var analyzer = new ScopedWhereUsedAnalyzer(Context.DocumentService, analyzedType, FindTypeUsage, options); + var result = analyzer.PerformAnalysis(ct) + .Cast() + .Where(n => !allTypes.Contains(n.Member!.DeclaringType)) + .Distinct(AnalyzerEntityTreeNodeComparer.Instance) + .Concat(FindGlobalUsage(analyzer.AllModules)); + foreach (var n in result) + yield return n; + + allTypes = null; + } + + IEnumerable FindGlobalUsage(List allModules) { + var analyzedAssemblies = new HashSet(); + foreach (var module in allModules) { + bool analyzedTypeIsExported = false; + foreach (var et in module.ExportedTypes) { + if (et.MovedToAnotherAssembly && et.Name == analyzedType.Name && et.Namespace == analyzedType.Namespace && et.Resolve() == analyzedType) { + analyzedTypeIsExported = true; + break; + } + } + if (module.Assembly is AssemblyDef asm && analyzedAssemblies.Add(asm)) { + if (analyzedTypeIsExported || IsUsedInCustomAttributes(asm)) + yield return new AssemblyNode(asm) { Context = Context }; + } + if (IsUsedInCustomAttributes(module)) + yield return new ModuleNode(module) { Context = Context }; + } + } + + IEnumerable FindTypeUsage(TypeDef? type) { + if (type is null) + yield break; + if (new SigComparer().Equals(type, analyzedType)) + yield break; + if (isComType && ComUtils.ComEquals(type, ref comGuid)) { + Debug2.Assert(allTypes is not null); + lock (allTypes) + allTypes.Add(type); + yield break; + } + + if (IsUsedInTypeDef(type)) + yield return new TypeNode(type) { Context = Context }; + + foreach (var field in type.Fields.Where(IsUsedInFieldDef)) + yield return new FieldNode(field) { Context = Context }; + + foreach (var method in type.Methods) { + SourceRef? sourceRef = null; + if (IsUsedInMethodDef(method, ref sourceRef)) + yield return HandleSpecialMethodNode(method, sourceRef); + } + + foreach (var property in type.Properties) { + if (IsUsedInCustomAttributes(property)) + yield return new PropertyNode(property) { Context = Context }; + } + + foreach (var @event in type.Events) { + if (IsUsedInCustomAttributes(@event)) + yield return new EventNode(@event) { Context = Context }; + } + } + + EntityNode HandleSpecialMethodNode(MethodDef method, SourceRef? sourceRef) { + var property = method.DeclaringType.Properties.FirstOrDefault(p => (object?)p.GetMethod == method || (object?)p.SetMethod == method); + if (property is not null) + return new PropertyNode(property) { Context = Context, SourceRef = sourceRef }; + + var @event = method.DeclaringType.Events.FirstOrDefault(p => (object?)p.AddMethod == method || (object?)p.RemoveMethod == method || (object?)p.InvokeMethod == method); + if (@event is not null) + return new EventNode(@event) { Context = Context, SourceRef = sourceRef }; + + return new MethodNode(method) { Context = Context, SourceRef = sourceRef }; + } + + bool IsUsedInTypeRefs(IEnumerable types) => types.Any(IsUsedInTypeRef); + + bool IsUsedInTypeRef(ITypeDefOrRef? type) { + if (type is null) + return false; + + return TypeMatches(type.DeclaringType) + || TypeMatches(type); + } + + bool IsUsedInTypeDef(TypeDef? type) { + if (type is null) + return false; + + return IsUsedInTypeRef(type) + || TypeMatches(type.BaseType) + || IsUsedInTypeRefs(type.Interfaces.Select(ii => ii.Interface)) + || IsUsedInCustomAttributes(type); + } + + bool IsUsedInCustomAttributes(IHasCustomAttribute? hca) { + if (hca is null) + return false; + foreach (var ca in hca.GetCustomAttributes()) { + if (IsUsedInMethodRef(ca.Constructor)) + return true; + foreach (var arg in ca.ConstructorArguments) { + if (IsUsed(arg, 0)) + return true; + } + foreach (var arg in ca.NamedArguments) { + if (IsUsed(arg.Argument, 0)) + return true; + if (TypeMatches(arg.Type)) + return true; + } + } + return false; + } + + const int maxRecursion = 20; + bool IsUsed(CAArgument arg, int recursionCounter) { + if (recursionCounter > maxRecursion) + return false; + if (TypeMatches(arg.Type)) + return true; + return ValueMatches(arg.Value, recursionCounter + 1); + } + + bool ValueMatches(object? value, int recursionCounter) { + if (recursionCounter > maxRecursion) + return false; + if (value is null) + return false; + if (value is TypeSig ts) + return TypeMatches(ts); + if (value is CAArgument arg) + return IsUsed(arg, recursionCounter + 1); + if (value is IList args) { + for (int i = 0; i < args.Count; i++) { + if (IsUsed(args[i], recursionCounter + 1)) + return true; + } + return false; + } + return false; + } + + bool IsUsedInFieldDef(FieldDef field) => + IsUsedInFieldRef(field) || IsUsedInCustomAttributes(field); + + bool IsUsedInFieldRef(IField? field) { + if (field is null || !field.IsField) + return false; + + return TypeMatches(field.DeclaringType) + || TypeMatches(field.FieldSig.GetFieldType()); + } + + bool IsUsedInMethodRef(IMethod? method) { + if (method is null || !method.IsMethod) + return false; + + if (method is MethodSpec ms && ms.Instantiation is GenericInstMethodSig gims) { + foreach (var ga in gims.GenericArguments) { + if (TypeMatches(ga)) + return true; + } + } + + return TypeMatches(method.DeclaringType) + || TypeMatches(method.MethodSig.GetRetType()) + || IsUsedInMethodParameters(method.GetParameters()); + } + + bool IsUsedInMethodDef(MethodDef method, ref SourceRef? sourceRef) { + if (IsUsedInMethodRef(method) || IsUsedInMethodBody(method, ref sourceRef) || IsUsedInCustomAttributes(method)) + return true; + foreach (var pd in method.ParamDefs) { + if (IsUsedInCustomAttributes(pd)) + return true; + } + return false; + } + + bool IsUsedInMethodBody(MethodDef? method, ref SourceRef? sourceRef) { + if (method is null) + return false; + if (method.Body is null) + return false; + + foreach (var instruction in method.Body.Instructions) { + ITypeDefOrRef? tr = instruction.Operand as ITypeDefOrRef; + if (IsUsedInTypeRef(tr)) { + sourceRef = new SourceRef(method, instruction.Offset, instruction.Operand as IMDTokenProvider); + return true; + } + IField? fr = instruction.Operand as IField; + if (IsUsedInFieldRef(fr)) { + sourceRef = new SourceRef(method, instruction.Offset, instruction.Operand as IMDTokenProvider); + return true; + } + IMethod? mr = instruction.Operand as IMethod; + if (IsUsedInMethodRef(mr)) { + sourceRef = new SourceRef(method, instruction.Offset, instruction.Operand as IMDTokenProvider); + return true; + } + } + foreach (var local in method.Body.Variables) { + if (TypeMatches(local.Type)) { + sourceRef = new SourceRef(method, null, null); + return true; + } + } + foreach (var eh in method.Body.ExceptionHandlers) { + if (TypeMatches(eh.CatchType)) { + sourceRef = new SourceRef(method, null, null); + return true; + } + } + + return false; + } + + bool IsUsedInMethodParameters(IEnumerable parameters) => parameters.Any(IsUsedInMethodParameter); + bool IsUsedInMethodParameter(Parameter parameter) => !parameter.IsHiddenThisParameter && TypeMatches(parameter.Type); + + bool TypeMatches(IType? tref) => TypeMatches(tref, 0); + bool TypeMatches(IType? tref, int level) { + if (level >= 100) + return false; + if (isComType && tref.Resolve() is TypeDef td && ComUtils.ComEquals(td, ref comGuid)) + return true; + if (tref is not null) { + if (new SigComparer().Equals(analyzedType, tref.GetScopeType())) + return true; + if (tref is TypeSig ts) { + switch (ts) { + case TypeDefOrRefSig tdr: + if (TypeMatches(tdr.TypeDefOrRef, level + 1)) + return true; + break; + case FnPtrSig fnptr: + if (fnptr.MethodSig is MethodSig msig) { + if (TypeMatches(msig.RetType, level + 1)) + return true; + foreach (var p in msig.Params) { + if (TypeMatches(p, level + 1)) + return true; + } + if (msig.ParamsAfterSentinel is not null) { + foreach (var p in msig.ParamsAfterSentinel) { + if (TypeMatches(p, level + 1)) + return true; + } + } + } + break; + case GenericInstSig gis: + if (TypeMatches(gis.GenericType, level + 1)) + return true; + foreach (var ga in gis.GenericArguments) { + if (TypeMatches(ga, level + 1)) + return true; + } + break; + case PtrSig ps: + if (TypeMatches(ps.Next, level + 1)) + return true; + break; + case ByRefSig brs: + if (TypeMatches(brs.Next, level + 1)) + return true; + break; + case ArraySigBase asb: + if (TypeMatches(asb.Next, level + 1)) + return true; + break; + case ModifierSig ms: + if (TypeMatches(ms.Modifier, level + 1)) + return true; + if (TypeMatches(ms.Next, level + 1)) + return true; + break; + case PinnedSig ps: + if (TypeMatches(ps.Next, level + 1)) + return true; + break; + } + } + else if (tref is TypeSpec typeSpec) { + if (TypeMatches(typeSpec.TypeSig, level + 1)) + return true; + } + } + return false; + } + + public static bool CanShow(TypeDef? type) => type is not null; + } + + sealed class AnalyzerEntityTreeNodeComparer : IEqualityComparer { + public static readonly AnalyzerEntityTreeNodeComparer Instance = new AnalyzerEntityTreeNodeComparer(); + AnalyzerEntityTreeNodeComparer() { } + public bool Equals([AllowNull] EntityNode x, [AllowNull] EntityNode y) => (object?)x?.Member == y?.Member; + public int GetHashCode([DisallowNull] EntityNode node) => node.Member?.GetHashCode() ?? 0; + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeNodes/VirtualMethodUsedByNode.cs b/Extensions/dnSpy.Analyzer/TreeNodes/VirtualMethodUsedByNode.cs new file mode 100644 index 0000000..e3ff623 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeNodes/VirtualMethodUsedByNode.cs @@ -0,0 +1,164 @@ +// 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 System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using dnSpy.Analyzer.Properties; +using dnSpy.Contracts.Decompiler; +using dnSpy.Contracts.Text; + +namespace dnSpy.Analyzer.TreeNodes { + sealed class VirtualMethodUsedByNode : SearchNode { + readonly MethodDef analyzedMethod; + readonly bool isSetter; + PropertyDef? property; + ConcurrentDictionary? foundMethods; + MethodDef? baseMethod; + Guid comGuid; + bool isComType; + int vtblIndex; + + public VirtualMethodUsedByNode(MethodDef analyzedMethod, bool isSetter) { + this.analyzedMethod = analyzedMethod ?? throw new ArgumentNullException(nameof(analyzedMethod)); + this.isSetter = isSetter; + } + + protected override void Write(ITextColorWriter output, IDecompiler decompiler) => + output.Write(BoxedTextColor.Text, dnSpy_Analyzer_Resources.UsedByTreeNode); + + protected override IEnumerable FetchChildren(CancellationToken ct) { + InitializeAnalyzer(); + + if (isSetter) + property = analyzedMethod.DeclaringType.Properties.FirstOrDefault(a => a.SetMethod == analyzedMethod); + + var includeAllModules = property is not null && CustomAttributesUtils.IsPseudoCustomAttributeType(analyzedMethod.DeclaringType); + ComUtils.GetMemberInfo(analyzedMethod, out isComType, out comGuid, out vtblIndex); + includeAllModules |= isComType; + var options = ScopedWhereUsedAnalyzerOptions.None; + if (includeAllModules) + options |= ScopedWhereUsedAnalyzerOptions.IncludeAllModules; + if (isComType) + options |= ScopedWhereUsedAnalyzerOptions.ForcePublic; + var analyzer = new ScopedWhereUsedAnalyzer(Context.DocumentService, analyzedMethod, FindReferencesInType, options); + foreach (var child in analyzer.PerformAnalysis(ct)) { + yield return child; + } + + if (property is not null) { + var hash = new HashSet(); + foreach (var module in analyzer.AllModules) { + if (module.Assembly is AssemblyDef asm && hash.Add(module.Assembly)) { + foreach (var node in FieldAccessNode.CheckCustomAttributeNamedArgumentWrite(Context, asm, property)) + yield return node; + } + foreach (var node in FieldAccessNode.CheckCustomAttributeNamedArgumentWrite(Context, module, property)) + yield return node; + } + } + + ReleaseAnalyzer(); + } + + void InitializeAnalyzer() { + foundMethods = new ConcurrentDictionary(); + + var baseMethods = TypesHierarchyHelpers.FindBaseMethods(analyzedMethod).ToArray(); + if (baseMethods.Length > 0) { + baseMethod = baseMethods[baseMethods.Length - 1]; + } + else + baseMethod = analyzedMethod; + } + + void ReleaseAnalyzer() { + foundMethods = null; + baseMethod = null; + } + + IEnumerable FindReferencesInType(TypeDef type) { + string name = analyzedMethod.Name; + foreach (MethodDef method in type.Methods) { + if (!method.HasBody) + continue; + Instruction? foundInstr = null; + foreach (Instruction instr in method.Body.Instructions) { + if (!(instr.Operand is IMethod mr) || mr.IsField) + continue; + MethodDef? md = null; + + if (isComType) { + md ??= mr.ResolveMethodDef(); + if (md is not null) { + ComUtils.GetMemberInfo(md, out bool otherIsComType, out var otherComGuid, out int otherVtblIndex); + if (otherIsComType && comGuid == otherComGuid && vtblIndex == otherVtblIndex) { + foundInstr = instr; + break; + } + } + } + + if (mr.Name == name) { + // explicit call to the requested method + if (Helpers.IsReferencedBy(analyzedMethod.DeclaringType, mr.DeclaringType) + && CheckEquals(md ??= mr.ResolveMethodDef(), analyzedMethod)) { + foundInstr = instr; + break; + } + // virtual call to base method + if (instr.OpCode.Code == Code.Callvirt || instr.OpCode.Code == Code.Ldvirtftn) { + md ??= mr.ResolveMethodDef(); + if (md is null) { + // cannot resolve the operand, so ignore this method + break; + } + if (CheckEquals(md, baseMethod)) { + foundInstr = instr; + break; + } + } + } + } + + if (foundInstr is not null) { + if (GetOriginalCodeLocation(method) is MethodDef codeLocation && !HasAlreadyBeenFound(codeLocation)) { + var node = new MethodNode(codeLocation) { Context = Context }; + if (codeLocation == method) + node.SourceRef = new SourceRef(method, foundInstr.Offset, foundInstr.Operand as IMDTokenProvider); + yield return node; + } + } + } + + if (property is not null) { + foreach (var node in FieldAccessNode.CheckCustomAttributeNamedArgumentWrite(Context, type, property)) { + if (node is MethodNode methodNode && methodNode.Member is MethodDef method && HasAlreadyBeenFound(method)) + continue; + yield return node; + } + } + } + + bool HasAlreadyBeenFound(MethodDef method) => !foundMethods!.TryAdd(method, 0); + } +} diff --git a/Extensions/dnSpy.Analyzer/TreeTraversal.cs b/Extensions/dnSpy.Analyzer/TreeTraversal.cs new file mode 100644 index 0000000..ea8bf07 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/TreeTraversal.cs @@ -0,0 +1,105 @@ +// Copyright (c) 2010-2013 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 System.Collections.Generic; + +namespace dnSpy.Analyzer { + /// + /// Static helper methods for traversing trees. + /// + static class TreeTraversal { + /// + /// Converts a tree data structure into a flat list by traversing it in pre-order. + /// + /// The root element of the tree. + /// The function that gets the children of an element. + /// Iterator that enumerates the tree structure in pre-order. + public static IEnumerable PreOrder(T root, Func> recursion) => PreOrder(new T[] { root }, recursion); + + /// + /// Converts a tree data structure into a flat list by traversing it in pre-order. + /// + /// The root elements of the forest. + /// The function that gets the children of an element. + /// Iterator that enumerates the tree structure in pre-order. + public static IEnumerable PreOrder(IEnumerable input, Func> recursion) { + Stack> stack = new Stack>(); + try { + stack.Push(input.GetEnumerator()); + while (stack.Count > 0) { + while (stack.Peek().MoveNext()) { + T element = stack.Peek().Current; + yield return element; + IEnumerable children = recursion(element); + if (children is not null) { + stack.Push(children.GetEnumerator()); + } + } + stack.Pop().Dispose(); + } + } + finally { + while (stack.Count > 0) { + stack.Pop().Dispose(); + } + } + } + + /// + /// Converts a tree data structure into a flat list by traversing it in post-order. + /// + /// The root element of the tree. + /// The function that gets the children of an element. + /// Iterator that enumerates the tree structure in post-order. + public static IEnumerable PostOrder(T root, Func> recursion) => PostOrder(new T[] { root }, recursion); + + /// + /// Converts a tree data structure into a flat list by traversing it in post-order. + /// + /// The root elements of the forest. + /// The function that gets the children of an element. + /// Iterator that enumerates the tree structure in post-order. + public static IEnumerable PostOrder(IEnumerable input, Func> recursion) { + Stack> stack = new Stack>(); + try { + stack.Push(input.GetEnumerator()); + while (stack.Count > 0) { + while (stack.Peek().MoveNext()) { + T element = stack.Peek().Current; + IEnumerable children = recursion(element); + if (children is not null) { + stack.Push(children.GetEnumerator()); + } + else { + yield return element; + } + } + stack.Pop().Dispose(); + if (stack.Count > 0) + yield return stack.Peek().Current; + } + } + finally { + while (stack.Count > 0) { + stack.Pop().Dispose(); + } + } + } + } +} diff --git a/Extensions/dnSpy.Analyzer/dnSpy.Analyzer.csproj b/Extensions/dnSpy.Analyzer/dnSpy.Analyzer.csproj new file mode 100644 index 0000000..fc4ab26 --- /dev/null +++ b/Extensions/dnSpy.Analyzer/dnSpy.Analyzer.csproj @@ -0,0 +1,42 @@ + + + + + + Copyright 2011-2014 AlphaSierraPapa for the SharpDevelop Team + $(DnSpyAssemblyVersion) + $(DnSpyAssemblyInformationalVersion) + + dnSpy.Analyzer.x + True + ..\..\dnSpy.snk + ..\..\dnSpy\dnSpy\bin\$(Configuration)\ + enable + true + + + + + True + True + dnSpy.Analyzer.Resources.resx + + + + + + PublicResXFileCodeGenerator + dnSpy.Analyzer.Resources.Designer.cs + + + + + + + + + + + + + diff --git a/Extensions/dnSpy.AsmEditor/Assembly/AssemblyCommands.cs b/Extensions/dnSpy.AsmEditor/Assembly/AssemblyCommands.cs new file mode 100644 index 0000000..1e458dc --- /dev/null +++ b/Extensions/dnSpy.AsmEditor/Assembly/AssemblyCommands.cs @@ -0,0 +1,441 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Diagnostics; +using System.Linq; +using dnlib.DotNet; +using dnlib.PE; +using dnSpy.AsmEditor.Commands; +using dnSpy.AsmEditor.Properties; +using dnSpy.AsmEditor.SaveModule; +using dnSpy.AsmEditor.UndoRedo; +using dnSpy.Contracts.Controls; +using dnSpy.Contracts.Documents; +using dnSpy.Contracts.Documents.Tabs; +using dnSpy.Contracts.Documents.TreeView; +using dnSpy.Contracts.Extension; +using dnSpy.Contracts.Images; +using dnSpy.Contracts.Menus; +using dnSpy.Contracts.TreeView; +using dnSpy.Contracts.Utilities; + +namespace dnSpy.AsmEditor.Assembly { + [ExportAutoLoaded] + sealed class CommandLoader : IAutoLoaded { + [ImportingConstructor] + CommandLoader(IWpfCommandService wpfCommandService, IDocumentTabService documentTabService, RemoveAssemblyCommand.EditMenuCommand removeCmd, AssemblySettingsCommand.EditMenuCommand settingsCmd) { + wpfCommandService.AddRemoveCommand(removeCmd); + wpfCommandService.AddSettingsCommand(documentTabService, settingsCmd, null); + } + } + + [ExportMenuItem(Header = "res:DisableMMapIOCommand", Group = MenuConstants.GROUP_CTX_DOCUMENTS_OTHER, Order = 50)] + sealed class DisableMemoryMappedIOCommand : MenuItemBase { + public override bool IsVisible(IMenuItemContext context) => + context.CreatorObject.Guid == new Guid(MenuConstants.GUIDOBJ_DOCUMENTS_TREEVIEW_GUID) && + (context.Find() ?? Array.Empty()).Any(a => GetDocument(a) is not null); + + static IDsDocument? GetDocument(TreeNodeData node) { + var fileNode = node as DsDocumentNode; + if (fileNode is null) + return null; + + var peImage = fileNode.Document.PEImage; + if (peImage is null) + peImage = (fileNode.Document.ModuleDef as ModuleDefMD)?.Metadata?.PEImage; + + return (peImage as IInternalPEImage)?.IsMemoryMappedIO == true ? fileNode.Document : null; + } + + public override void Execute(IMenuItemContext context) { + if (context.CreatorObject.Guid != new Guid(MenuConstants.GUIDOBJ_DOCUMENTS_TREEVIEW_GUID)) + return; + var asms = new List(); + foreach (var node in (context.Find() ?? Array.Empty())) { + var file = GetDocument(node); + if (file is not null) + asms.Add(file); + } + foreach (var asm in asms) + (asm.PEImage as IInternalPEImage)?.UnsafeDisableMemoryMappedIO(); + } + } + + [DebuggerDisplay("{Description}")] + sealed class RemoveAssemblyCommand : IUndoCommand { + [ExportMenuItem(Header = "res:RemoveAssemblyCommand", Icon = DsImagesAttribute.Cancel, InputGestureText = "res:DeleteCommandKey", Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_DELETE, Order = 0)] + sealed class DocumentsCommand : DocumentsContextMenuHandler { + readonly Lazy undoCommandService; + readonly Lazy documentSaver; + readonly IAppService appService; + + [ImportingConstructor] + DocumentsCommand(Lazy undoCommandService, Lazy documentSaver, IAppService appService) { + this.undoCommandService = undoCommandService; + this.documentSaver = documentSaver; + this.appService = appService; + } + + public override bool IsVisible(AsmEditorContext context) => RemoveAssemblyCommand.CanExecute(context.Nodes); + public override void Execute(AsmEditorContext context) => RemoveAssemblyCommand.Execute(undoCommandService, documentSaver, appService, context.Nodes); + public override string? GetHeader(AsmEditorContext context) => RemoveAssemblyCommand.GetHeader(context.Nodes); + } + + [Export, ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:RemoveAssemblyCommand", Icon = DsImagesAttribute.Cancel, InputGestureText = "res:DeleteCommandKey", Group = MenuConstants.GROUP_APP_MENU_EDIT_ASMED_DELETE, Order = 0)] + internal sealed class EditMenuCommand : EditMenuHandler { + readonly Lazy undoCommandService; + readonly Lazy documentSaver; + readonly IAppService appService; + + [ImportingConstructor] + EditMenuCommand(Lazy undoCommandService, Lazy documentSaver, IAppService appService) + : base(appService.DocumentTreeView) { + this.undoCommandService = undoCommandService; + this.documentSaver = documentSaver; + this.appService = appService; + } + + public override bool IsVisible(AsmEditorContext context) => RemoveAssemblyCommand.CanExecute(context.Nodes); + public override void Execute(AsmEditorContext context) => RemoveAssemblyCommand.Execute(undoCommandService, documentSaver, appService, context.Nodes); + public override string? GetHeader(AsmEditorContext context) => RemoveAssemblyCommand.GetHeader(context.Nodes); + } + + static string GetHeader(TreeNodeData[] nodes) { + if (nodes.Length == 1) + return string.Format(dnSpy_AsmEditor_Resources.RemoveCommand, UIUtilities.EscapeMenuItemHeader(nodes[0].ToString()!)); + return string.Format(dnSpy_AsmEditor_Resources.RemoveAssembliesCommand, nodes.Length); + } + + static bool CanExecute(DocumentTreeNodeData[] nodes) => + nodes.Length > 0 && + nodes.All(n => n is DsDocumentNode && n.TreeNode.Parent == n.Context.DocumentTreeView.TreeView.Root); + + internal static void Execute(Lazy undoCommandService, Lazy documentSaver, IAppService appService, DocumentTreeNodeData[] nodes) { + if (!CanExecute(nodes)) + return; + + var asmNodes = nodes.Cast().ToArray(); + var files = asmNodes.SelectMany(a => a.Document.GetAllChildrenAndSelf()); + if (!documentSaver.Value.AskUserToSaveIfModified(files)) + return; + + var keepNodes = new List(); + var freeNodes = new List(); + var onlyInRedoHistory = new List(); + foreach (var info in GetUndoRedoInfo(undoCommandService.Value, asmNodes)) { + if (!info.IsInUndo && !info.IsInRedo) { + // This asm is safe to remove + freeNodes.Add(info.Node); + } + else if (!info.IsInUndo && info.IsInRedo) { + // If we add a RemoveAssemblyCommand, the redo history will be cleared, so this + // assembly will be cleared from the history and don't need to be kept. + onlyInRedoHistory.Add(info.Node); + } + else { + // The asm is in the undo history, and maybe in the redo history. We must keep it. + keepNodes.Add(info.Node); + } + } + + if (keepNodes.Count > 0 || onlyInRedoHistory.Count > 0) { + // We can't free the asm since older commands might reference it so we must record + // it in the history. The user can click Clear History to free everything. + foreach (var node in keepNodes) { + foreach (var f in node.Document.GetAllChildrenAndSelf()) + MemoryMappedIOHelper.DisableMemoryMappedIO(f); + } + if (keepNodes.Count != 0) + undoCommandService.Value.Add(new RemoveAssemblyCommand(appService.DocumentTreeView, keepNodes.ToArray())); + else + undoCommandService.Value.ClearRedo(); + // Redo history was cleared + FreeAssemblies(onlyInRedoHistory); + } + + FreeAssemblies(freeNodes); + } + + static void FreeAssemblies(IList nodes) { + if (nodes.Count == 0) + return; + var docTreeView = nodes[0].Context.DocumentTreeView; + if (nodes.Count == docTreeView.TreeView.Root.Children.Count) { + var hash1 = new HashSet(docTreeView.TreeView.Root.Children.Select(a => (DsDocumentNode)a.Data)); + var hash2 = new HashSet(nodes); + if (hash1.Count == hash2.Count && hash1.Count == nodes.Count) { + docTreeView.TreeView.SelectItems(Array.Empty()); + docTreeView.DocumentService.Clear(); + return; + } + } + docTreeView.Remove(nodes); + } + + readonly struct UndoRedoInfo { + public readonly bool IsInUndo; + public readonly bool IsInRedo; + public readonly DsDocumentNode Node; + + public UndoRedoInfo(DsDocumentNode node, bool isInUndo, bool isInRedo) { + IsInUndo = isInUndo; + IsInRedo = isInRedo; + Node = node; + } + } + + static IEnumerable GetUndoRedoInfo(IUndoCommandService undoCommandService, IEnumerable nodes) { + var modifiedUndoAsms = new HashSet(undoCommandService.UndoObjects); + var modifiedRedoAsms = new HashSet(undoCommandService.RedoObjects); + foreach (var node in nodes) { + var uo = undoCommandService.GetUndoObject(node.Document); + bool isInUndo = modifiedUndoAsms.Contains(uo!); + bool isInRedo = modifiedRedoAsms.Contains(uo!); + yield return new UndoRedoInfo(node, isInUndo, isInRedo); + } + } + + RootDocumentNodeCreator[] savedStates; + + RemoveAssemblyCommand(IDocumentTreeView documentTreeView, DsDocumentNode[] asmNodes) { + savedStates = new RootDocumentNodeCreator[asmNodes.Length]; + for (int i = 0; i < savedStates.Length; i++) + savedStates[i] = new RootDocumentNodeCreator(documentTreeView, asmNodes[i]); + } + + public string Description => dnSpy_AsmEditor_Resources.RemoveAssemblyCommand; + + public void Execute() { + for (int i = 0; i < savedStates.Length; i++) + savedStates[i].Remove(); + } + + public void Undo() { + for (int i = savedStates.Length - 1; i >= 0; i--) + savedStates[i].Add(); + } + + public IEnumerable ModifiedObjects { + get { + foreach (var savedState in savedStates) + yield return savedState.DocumentNode; + } + } + } + + [DebuggerDisplay("{Description}")] + sealed class AssemblySettingsCommand : IUndoCommand { + [ExportMenuItem(Header = "res:EditAssemblyCommand", Icon = DsImagesAttribute.Settings, InputGestureText = "res:ShortcutKeyAltEnter", Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_SETTINGS, Order = 0)] + sealed class DocumentsCommand : DocumentsContextMenuHandler { + readonly Lazy undoCommandService; + readonly IAppService appService; + + [ImportingConstructor] + DocumentsCommand(Lazy undoCommandService, IAppService appService) { + this.undoCommandService = undoCommandService; + this.appService = appService; + } + + public override bool IsVisible(AsmEditorContext context) => AssemblySettingsCommand.CanExecute(context.Nodes); + public override void Execute(AsmEditorContext context) => AssemblySettingsCommand.Execute(undoCommandService, appService, context.Nodes); + } + + [Export, ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:EditAssemblyCommand", Icon = DsImagesAttribute.Settings, InputGestureText = "res:ShortcutKeyAltEnter", Group = MenuConstants.GROUP_APP_MENU_EDIT_ASMED_SETTINGS, Order = 0)] + internal sealed class EditMenuCommand : EditMenuHandler { + readonly Lazy undoCommandService; + readonly IAppService appService; + + [ImportingConstructor] + EditMenuCommand(Lazy undoCommandService, IAppService appService) + : base(appService.DocumentTreeView) { + this.undoCommandService = undoCommandService; + this.appService = appService; + } + + public override bool IsVisible(AsmEditorContext context) => AssemblySettingsCommand.CanExecute(context.Nodes); + public override void Execute(AsmEditorContext context) => AssemblySettingsCommand.Execute(undoCommandService, appService, context.Nodes); + } + + static bool CanExecute(DocumentTreeNodeData[] nodes) => + nodes is not null && + nodes.Length == 1 && + nodes[0] is AssemblyDocumentNode; + + static void Execute(Lazy undoCommandService, IAppService appService, DocumentTreeNodeData[] nodes) { + if (!CanExecute(nodes)) + return; + + var asmNode = (AssemblyDocumentNode)nodes[0]; + var module = asmNode.Document.ModuleDef!; + + var data = new AssemblyOptionsVM(new AssemblyOptions(asmNode.Document.AssemblyDef!), module, appService.DecompilerService); + var win = new AssemblyOptionsDlg(); + win.DataContext = data; + win.Owner = appService.MainWindow; + if (win.ShowDialog() != true) + return; + + undoCommandService.Value.Add(new AssemblySettingsCommand(asmNode, data.CreateAssemblyOptions())); + } + + readonly AssemblyDocumentNode asmNode; + readonly AssemblyOptions newOptions; + readonly AssemblyOptions origOptions; + readonly AssemblyRefInfo[]? assemblyRefInfos; + + readonly struct AssemblyRefInfo { + public readonly AssemblyRef AssemblyRef; + public readonly UTF8String OrigName; + public readonly PublicKeyBase OrigPublicKeyOrToken; + + public AssemblyRefInfo(AssemblyRef asmRef) { + AssemblyRef = asmRef; + OrigName = asmRef.Name; + OrigPublicKeyOrToken = asmRef.PublicKeyOrToken; + } + } + + AssemblySettingsCommand(AssemblyDocumentNode asmNode, AssemblyOptions newOptions) { + this.asmNode = asmNode; + this.newOptions = newOptions; + origOptions = new AssemblyOptions(asmNode.Document.AssemblyDef!); + + if (newOptions.Name != origOptions.Name) + assemblyRefInfos = RefFinder.FindAssemblyRefsToThisModule(asmNode.Document.ModuleDef!).Where(a => AssemblyNameComparer.NameAndPublicKeyTokenOnly.Equals(a, asmNode.Document.AssemblyDef)).Select(a => new AssemblyRefInfo(a)).ToArray(); + } + + public string Description => dnSpy_AsmEditor_Resources.EditAssemblyCommand2; + + public void Execute() { + newOptions.CopyTo(asmNode.Document.AssemblyDef!); + if (assemblyRefInfos is not null) { + var pkt = newOptions.PublicKey.Token; + foreach (var info in assemblyRefInfos) { + info.AssemblyRef.Name = newOptions.Name; + if (info.AssemblyRef.PublicKeyOrToken is PublicKeyToken) + info.AssemblyRef.PublicKeyOrToken = pkt; + else + info.AssemblyRef.PublicKeyOrToken = newOptions.PublicKey; + } + } + asmNode.TreeNode.RefreshUI(); + } + + public void Undo() { + origOptions.CopyTo(asmNode.Document.AssemblyDef!); + if (assemblyRefInfos is not null) { + foreach (var info in assemblyRefInfos) { + info.AssemblyRef.Name = info.OrigName; + info.AssemblyRef.PublicKeyOrToken = info.OrigPublicKeyOrToken; + } + } + asmNode.TreeNode.TreeView.SelectItems(new[] { asmNode }); + asmNode.TreeNode.RefreshUI(); + } + + public IEnumerable ModifiedObjects { + get { yield return asmNode; } + } + } + + [DebuggerDisplay("{Description}")] + sealed class CreateAssemblyCommand : IUndoCommand { + [ExportMenuItem(Header = "res:CreateAssemblyCommand", Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_NEW, Order = 0)] + sealed class DocumentsCommand : DocumentsContextMenuHandler { + readonly Lazy undoCommandService; + readonly IAppService appService; + + [ImportingConstructor] + DocumentsCommand(Lazy undoCommandService, IAppService appService) { + this.undoCommandService = undoCommandService; + this.appService = appService; + } + + public override bool IsVisible(AsmEditorContext context) => CreateAssemblyCommand.CanExecute(context.Nodes); + public override void Execute(AsmEditorContext context) => CreateAssemblyCommand.Execute(undoCommandService, appService, context.Nodes); + } + + [ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:CreateAssemblyCommand", Group = MenuConstants.GROUP_APP_MENU_EDIT_ASMED_NEW, Order = 0)] + sealed class EditMenuCommand : EditMenuHandler { + readonly Lazy undoCommandService; + readonly IAppService appService; + + [ImportingConstructor] + EditMenuCommand(Lazy undoCommandService, IAppService appService) + : base(appService.DocumentTreeView) { + this.undoCommandService = undoCommandService; + this.appService = appService; + } + + public override bool IsVisible(AsmEditorContext context) => CreateAssemblyCommand.CanExecute(context.Nodes); + public override void Execute(AsmEditorContext context) => CreateAssemblyCommand.Execute(undoCommandService, appService, context.Nodes); + } + + static bool CanExecute(DocumentTreeNodeData[] nodes) => + nodes is not null && + (nodes.Length == 0 || nodes[0] is DsDocumentNode); + + static void Execute(Lazy undoCommandService, IAppService appService, DocumentTreeNodeData[] nodes) { + if (!CanExecute(nodes)) + return; + + var newModule = new ModuleDefUser(); + + var data = new AssemblyOptionsVM(AssemblyOptions.Create("MyAssembly"), newModule, appService.DecompilerService); + data.CanShowClrVersion = true; + var win = new AssemblyOptionsDlg(); + win.Title = dnSpy_AsmEditor_Resources.CreateAssemblyCommand2; + win.DataContext = data; + win.Owner = appService.MainWindow; + if (win.ShowDialog() != true) + return; + + var cmd = new CreateAssemblyCommand(undoCommandService.Value, appService.DocumentTreeView, newModule, data.CreateAssemblyOptions()); + undoCommandService.Value.Add(cmd); + appService.DocumentTabService.FollowReference(cmd.fileNodeCreator.DocumentNode); + } + + readonly RootDocumentNodeCreator fileNodeCreator; + readonly IUndoCommandService undoCommandService; + + CreateAssemblyCommand(IUndoCommandService undoCommandService, IDocumentTreeView documentTreeView, ModuleDef newModule, AssemblyOptions options) { + this.undoCommandService = undoCommandService; + var module = Module.ModuleUtils.CreateModule(options.Name, Guid.NewGuid(), options.ClrVersion, ModuleKind.Dll, newModule); + options.CreateAssemblyDef(module).Modules.Add(module); + var file = DsDotNetDocument.CreateAssembly(DsDocumentInfo.CreateDocument(string.Empty), module, true); + fileNodeCreator = RootDocumentNodeCreator.CreateAssembly(documentTreeView, file); + } + + public string Description => dnSpy_AsmEditor_Resources.CreateAssemblyCommand2; + + public void Execute() { + fileNodeCreator.Add(); + undoCommandService.MarkAsModified(undoCommandService.GetUndoObject(fileNodeCreator.DocumentNode.Document)!); + } + + public void Undo() => fileNodeCreator.Remove(); + + public IEnumerable ModifiedObjects { + get { yield return fileNodeCreator.DocumentNode; } + } + } +} diff --git a/Extensions/dnSpy.AsmEditor/Assembly/AssemblyOptions.cs b/Extensions/dnSpy.AsmEditor/Assembly/AssemblyOptions.cs new file mode 100644 index 0000000..703145f --- /dev/null +++ b/Extensions/dnSpy.AsmEditor/Assembly/AssemblyOptions.cs @@ -0,0 +1,79 @@ +/* + Copyright (C) 2014-2019 de4dot@gmail.com + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; +using System.Collections.Generic; +using dnlib.DotNet; + +namespace dnSpy.AsmEditor.Assembly { + sealed class AssemblyOptions { + public AssemblyHashAlgorithm HashAlgorithm; + public Version Version; + public AssemblyAttributes Attributes; + public PublicKey PublicKey; + public UTF8String? Name; + public string? Culture; + public Module.ClrVersion ClrVersion; + public List CustomAttributes = new List(); + public List DeclSecurities = new List(); + + public AssemblyOptions() { + Version = null!; + PublicKey = null!; + } + + public AssemblyOptions(AssemblyDef asm) { + HashAlgorithm = asm.HashAlgorithm; + Version = asm.Version; + Attributes = asm.Attributes; + PublicKey = asm.PublicKey; + Name = asm.Name; + Culture = asm.Culture; + ClrVersion = Module.ClrVersion.DefaultVersion; + CustomAttributes.AddRange(asm.CustomAttributes); + DeclSecurities.AddRange(asm.DeclSecurities); + } + + public AssemblyDef CopyTo(AssemblyDef asm) { + asm.HashAlgorithm = HashAlgorithm; + asm.Version = Version; + asm.Attributes = Attributes; + asm.PublicKey = PublicKey; + asm.Name = Name ?? UTF8String.Empty; + asm.Culture = Culture; + asm.CustomAttributes.Clear(); + asm.CustomAttributes.AddRange(CustomAttributes); + asm.DeclSecurities.Clear(); + asm.DeclSecurities.AddRange(DeclSecurities); + return asm; + } + + public AssemblyDef CreateAssemblyDef(ModuleDef ownerModule) => ownerModule.UpdateRowId(CopyTo(new AssemblyDefUser())); + + public static AssemblyOptions Create(string name) => new AssemblyOptions { + HashAlgorithm = AssemblyHashAlgorithm.SHA1, + Version = new Version(0, 0, 0, 0), + Attributes = AssemblyAttributes.None, + PublicKey = new PublicKey(Array.Empty()), + Name = name, + Culture = string.Empty, + ClrVersion = Module.ClrVersion.DefaultVersion, + }; + } +} diff --git a/Extensions/dnSpy.AsmEditor/Assembly/AssemblyOptionsDlg.xaml b/Extensions/dnSpy.AsmEditor/Assembly/AssemblyOptionsDlg.xaml new file mode 100644 index 0000000..2c4e100 --- /dev/null +++ b/Extensions/dnSpy.AsmEditor/Assembly/AssemblyOptionsDlg.xaml @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +