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