/* 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)); } }