/* 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 dnlib.DotNet; using dnSpy.Contracts.Debugger; using dnSpy.Contracts.Debugger.Breakpoints.Code; using dnSpy.Contracts.Debugger.Breakpoints.Code.Dialogs; using dnSpy.Contracts.Debugger.DotNet.Code; using dnSpy.Contracts.Documents.Tabs.DocViewer; using dnSpy.Contracts.Images; using dnSpy.Contracts.Menus; using dnSpy.Contracts.Metadata; using dnSpy.Contracts.TreeView; namespace dnSpy.Debugger.DotNet.Breakpoints.Code.TextEditor { [Export(typeof(MethodBreakpointsService))] sealed class MethodBreakpointsService { readonly Lazy dbgManager; readonly Lazy moduleIdProvider; readonly Lazy dbgCodeBreakpointsService; readonly Lazy dbgDotNetCodeLocationFactory; readonly Lazy showCodeBreakpointSettingsService; [ImportingConstructor] MethodBreakpointsService(Lazy dbgManager, Lazy moduleIdProvider, Lazy dbgCodeBreakpointsService, Lazy dbgDotNetCodeLocationFactory, Lazy showCodeBreakpointSettingsService) { this.dbgManager = dbgManager; this.moduleIdProvider = moduleIdProvider; this.dbgCodeBreakpointsService = dbgCodeBreakpointsService; this.dbgDotNetCodeLocationFactory = dbgDotNetCodeLocationFactory; this.showCodeBreakpointSettingsService = showCodeBreakpointSettingsService; } public void Add(MethodDef[] methods, bool tracepoint) { DbgCodeBreakpointSettings settings; if (tracepoint) { var newSettings = showCodeBreakpointSettingsService.Value.Show(new DbgCodeBreakpointSettings { IsEnabled = true, Trace = new DbgCodeBreakpointTrace(string.Empty, true) }); if (newSettings is null) return; settings = newSettings.Value; } else settings = new DbgCodeBreakpointSettings { IsEnabled = true }; var list = new List(methods.Length); var existing = new HashSet(dbgCodeBreakpointsService.Value.Breakpoints.Select(a => a.Location).OfType()); List? objsToClose = null; foreach (var method in methods) { if (method.IsAbstract || method.Body is null) continue; var moduleId = moduleIdProvider.Value.Create(method.Module); var location = dbgDotNetCodeLocationFactory.Value.Create(moduleId, method.MDToken.Raw, 0); if (existing.Contains(location)) { if (objsToClose is null) objsToClose = new List(); objsToClose.Add(location); continue; } existing.Add(location); list.Add(new DbgCodeBreakpointInfo(location, settings)); } if (objsToClose is not null) dbgManager.Value.Close(objsToClose); dbgCodeBreakpointsService.Value.Add(list.ToArray()); } } static class AddClassBreakpointCtxMenuCommands { static IMDTokenProvider? GetReference(IMenuItemContext context, Guid guid) => AddMethodBreakpointCtxMenuCommands.GetReference(context, guid); static ITypeDefOrRef? GetTypeRef(IMenuItemContext context, Guid guid) => GetReference(context, guid) as ITypeDefOrRef; abstract class MenuItemCommon : MenuItemBase { readonly Lazy methodBreakpointsService; readonly bool tracepoint; readonly Guid guid; protected MenuItemCommon(Lazy methodBreakpointsService, bool tracepoint, string guid) { this.methodBreakpointsService = methodBreakpointsService; this.tracepoint = tracepoint; this.guid = Guid.Parse(guid); } public override bool IsVisible(IMenuItemContext context) => GetTypeRef(context, guid) is not null; public override bool IsEnabled(IMenuItemContext context) => GetTypeRef(context, guid) is not null; public override void Execute(IMenuItemContext context) { var type = GetTypeRef(context, guid)?.ResolveTypeDef(); if (type is null) return; methodBreakpointsService.Value.Add(type.Methods.ToArray(), tracepoint); } } [ExportMenuItem(Header = "res:AddClassBreakpointCommand", Icon = DsImagesAttribute.CheckDot, Group = MenuConstants.GROUP_CTX_DOCVIEWER_DEBUG, Order = 100)] sealed class BreakpointCodeCommand : MenuItemCommon { [ImportingConstructor] BreakpointCodeCommand(Lazy methodBreakpointsService) : base(methodBreakpointsService, false, MenuConstants.GUIDOBJ_DOCUMENTVIEWERCONTROL_GUID) { } } [ExportMenuItem(Header = "res:AddClassBreakpointCommand", Icon = DsImagesAttribute.CheckDot, Group = MenuConstants.GROUP_CTX_DOCUMENTS_DEBUG, Order = 100)] sealed class BreakpointAssemblyExplorerCommand : MenuItemCommon { [ImportingConstructor] BreakpointAssemblyExplorerCommand(Lazy methodBreakpointsService) : base(methodBreakpointsService, false, MenuConstants.GUIDOBJ_DOCUMENTS_TREEVIEW_GUID) { } } [ExportMenuItem(Header = "res:AddClassBreakpointCommand", Icon = DsImagesAttribute.CheckDot, Group = MenuConstants.GROUP_CTX_SEARCH_DEBUG, Order = 100)] sealed class BreakpointSearchCommand : MenuItemCommon { [ImportingConstructor] BreakpointSearchCommand(Lazy methodBreakpointsService) : base(methodBreakpointsService, false, MenuConstants.GUIDOBJ_SEARCH_GUID) { } } [ExportMenuItem(Header = "res:AddClassBreakpointCommand", Icon = DsImagesAttribute.CheckDot, Group = MenuConstants.GROUP_CTX_ANALYZER_DEBUG, Order = 100)] sealed class BreakpointAnalyzerCommand : MenuItemCommon { [ImportingConstructor] BreakpointAnalyzerCommand(Lazy methodBreakpointsService) : base(methodBreakpointsService, false, MenuConstants.GUIDOBJ_ANALYZER_TREEVIEW_GUID) { } } [ExportMenuItem(Header = "res:AddClassTracepointCommand", Group = MenuConstants.GROUP_CTX_DOCVIEWER_DEBUG, Order = 101)] sealed class TracepointCodeCommand : MenuItemCommon { [ImportingConstructor] TracepointCodeCommand(Lazy methodBreakpointsService) : base(methodBreakpointsService, true, MenuConstants.GUIDOBJ_DOCUMENTVIEWERCONTROL_GUID) { } } [ExportMenuItem(Header = "res:AddClassTracepointCommand", Group = MenuConstants.GROUP_CTX_DOCUMENTS_DEBUG, Order = 101)] sealed class TracepointAssemblyExplorerCommand : MenuItemCommon { [ImportingConstructor] TracepointAssemblyExplorerCommand(Lazy methodBreakpointsService) : base(methodBreakpointsService, true, MenuConstants.GUIDOBJ_DOCUMENTS_TREEVIEW_GUID) { } } [ExportMenuItem(Header = "res:AddClassTracepointCommand", Group = MenuConstants.GROUP_CTX_SEARCH_DEBUG, Order = 101)] sealed class TracepointSearchCommand : MenuItemCommon { [ImportingConstructor] TracepointSearchCommand(Lazy methodBreakpointsService) : base(methodBreakpointsService, true, MenuConstants.GUIDOBJ_SEARCH_GUID) { } } [ExportMenuItem(Header = "res:AddClassTracepointCommand", Group = MenuConstants.GROUP_CTX_ANALYZER_DEBUG, Order = 101)] sealed class TracepointAnalyzerCommand : MenuItemCommon { [ImportingConstructor] TracepointAnalyzerCommand(Lazy methodBreakpointsService) : base(methodBreakpointsService, true, MenuConstants.GUIDOBJ_ANALYZER_TREEVIEW_GUID) { } } } static class AddMethodBreakpointCtxMenuCommands { internal static IMDTokenProvider? GetReference(IMenuItemContext context, Guid guid) => GetReferences(context, guid).FirstOrDefault(); static IEnumerable GetReferences(IMenuItemContext context, Guid guid) { if (context.CreatorObject.Guid != guid) yield break; var @ref = context.Find(); if (@ref is not null) { var realRef = @ref.Reference; if (realRef is Parameter) realRef = ((Parameter)realRef).ParamDef; if (realRef is IMDTokenProvider) { yield return (IMDTokenProvider)realRef; yield break; } } var nodes = context.Find(); if (nodes is not null && nodes.Length != 0) { foreach (var node in nodes) { if (node is IMDTokenNode tokenNode && tokenNode.Reference is IMDTokenProvider tokenProvider) yield return tokenProvider; } yield break; } } static IMethod[]? GetMethodReferences(IMenuItemContext context, Guid guid) { var methods = new List(); foreach (var @ref in GetReferences(context, guid)) { switch (@ref) { case IMethod methodRef: if (methodRef.IsMethod) methods.Add(methodRef); break; case PropertyDef prop: { methods.AddRange(prop.GetMethods); methods.AddRange(prop.SetMethods); methods.AddRange(prop.OtherMethods); break; } case EventDef evt: if (evt.AddMethod is not null) methods.Add(evt.AddMethod); if (evt.RemoveMethod is not null) methods.Add(evt.RemoveMethod); if (evt.InvokeMethod is not null) methods.Add(evt.InvokeMethod); methods.AddRange(evt.OtherMethods); break; } } return methods.Count == 0 ? null : methods.ToArray(); } abstract class MenuItemCommon : MenuItemBase { readonly Lazy methodBreakpointsService; readonly bool tracepoint; readonly Guid guid; protected MenuItemCommon(Lazy methodBreakpointsService, bool tracepoint, string guid) { this.methodBreakpointsService = methodBreakpointsService; this.tracepoint = tracepoint; this.guid = Guid.Parse(guid); } public override bool IsVisible(IMenuItemContext context) => GetMethodReferences(context, guid) is not null; public override bool IsEnabled(IMenuItemContext context) => GetMethodReferences(context, guid) is not null; public override void Execute(IMenuItemContext context) { var methodRefs = GetMethodReferences(context, guid); if (methodRefs is null) return; methodBreakpointsService.Value.Add(methodRefs.Select(a => a.ResolveMethodDef()).Where(a => a is not null).ToArray(), tracepoint); } } [ExportMenuItem(Header = "res:AddMethodBreakpointCommand", Icon = DsImagesAttribute.CheckDot, Group = MenuConstants.GROUP_CTX_DOCVIEWER_DEBUG, Order = 150)] sealed class CodeCommand : MenuItemCommon { [ImportingConstructor] CodeCommand(Lazy methodBreakpointsService) : base(methodBreakpointsService, false, MenuConstants.GUIDOBJ_DOCUMENTVIEWERCONTROL_GUID) { } } [ExportMenuItem(Header = "res:AddMethodBreakpointCommand", Icon = DsImagesAttribute.CheckDot, Group = MenuConstants.GROUP_CTX_DOCUMENTS_DEBUG, Order = 150)] sealed class AssemblyExplorerCommand : MenuItemCommon { [ImportingConstructor] AssemblyExplorerCommand(Lazy methodBreakpointsService) : base(methodBreakpointsService, false, MenuConstants.GUIDOBJ_DOCUMENTS_TREEVIEW_GUID) { } } [ExportMenuItem(Header = "res:AddMethodBreakpointCommand", Icon = DsImagesAttribute.CheckDot, Group = MenuConstants.GROUP_CTX_SEARCH_DEBUG, Order = 150)] sealed class SearchCommand : MenuItemCommon { [ImportingConstructor] SearchCommand(Lazy methodBreakpointsService) : base(methodBreakpointsService, false, MenuConstants.GUIDOBJ_SEARCH_GUID) { } } [ExportMenuItem(Header = "res:AddMethodBreakpointCommand", Icon = DsImagesAttribute.CheckDot, Group = MenuConstants.GROUP_CTX_ANALYZER_DEBUG, Order = 150)] sealed class AnalyzerCommand : MenuItemCommon { [ImportingConstructor] AnalyzerCommand(Lazy methodBreakpointsService) : base(methodBreakpointsService, false, MenuConstants.GUIDOBJ_ANALYZER_TREEVIEW_GUID) { } } } }