Skip to content

Commit 9a40876

Browse files
authored
Merge pull request #472 from Washi1337/feature/string-xrefs
2 parents b85e4d3 + d9c1e4a commit 9a40876

24 files changed

+2326
-27
lines changed
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
/*
2+
Copyright (C) 2025 Washi
3+
4+
This file is part of dnSpy
5+
6+
dnSpy is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
dnSpy is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU General Public License for more details.
15+
16+
You should have received a copy of the GNU General Public License
17+
along with dnSpy. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
20+
using System;
21+
using System.Collections.Generic;
22+
using System.ComponentModel.Composition;
23+
using System.Linq;
24+
using System.Runtime.InteropServices;
25+
using System.Windows;
26+
using System.Windows.Input;
27+
using dnlib.DotNet;
28+
using dnSpy.Contracts.Controls;
29+
using dnSpy.Contracts.Documents.Tabs;
30+
using dnSpy.Contracts.Documents.TreeView;
31+
using dnSpy.Contracts.Extension;
32+
using dnSpy.Contracts.Images;
33+
using dnSpy.Contracts.Menus;
34+
using dnSpy.Contracts.ToolWindows.App;
35+
using dnSpy.Contracts.TreeView;
36+
37+
namespace dnSpy.StringSearcher {
38+
[ExportAutoLoaded]
39+
sealed class StringSearcherCommandLoader : IAutoLoaded {
40+
[ImportingConstructor]
41+
StringSearcherCommandLoader(IWpfCommandService wpfCommandService, Lazy<IStringReferencesService> analyzerService) {
42+
var cmds = wpfCommandService.GetCommands(new Guid(StringSearcherConstants.GUID_STRINGS_LISTBOX));
43+
cmds.Add(ApplicationCommands.Copy,
44+
(s, e) => CopyStringLiteralCommand.ExecuteInternal(analyzerService.Value.CurrentReference),
45+
(s, e) => e.CanExecute = analyzerService.Value.CurrentReference is not null
46+
);
47+
}
48+
}
49+
50+
abstract class FindStringReferencesInModuleCommand(IDsToolWindowService toolWindowService, IStringReferencesService stringReferencesService) : MenuItemBase {
51+
52+
public override bool IsVisible(IMenuItemContext context) => GetModules(context).Any();
53+
54+
public override void Execute(IMenuItemContext context) {
55+
var modules = GetModules(context).ToArray();
56+
if (modules.Length == 0)
57+
return;
58+
59+
toolWindowService.Show(StringReferencesToolWindowContent.THE_GUID);
60+
stringReferencesService.Analyze(modules);
61+
}
62+
63+
protected abstract IEnumerable<ModuleDef> GetModules(IMenuItemContext context);
64+
65+
[ExportMenuItem(Header = "res:FindStringReferencesInModuleCommand", Icon = DsImagesAttribute.Search, Group = MenuConstants.GROUP_CTX_DOCUMENTS_OTHER, Order = 0)]
66+
sealed class DocumentsCommand : FindStringReferencesInModuleCommand {
67+
68+
[ImportingConstructor]
69+
public DocumentsCommand(IDsToolWindowService toolWindowService, IStringReferencesService stringReferencesService)
70+
: base(toolWindowService, stringReferencesService) {
71+
}
72+
73+
protected override IEnumerable<ModuleDef> GetModules(IMenuItemContext context) {
74+
var nodes = context.Find<TreeNodeData[]>();
75+
if (nodes is null)
76+
return [];
77+
78+
return nodes.OfType<DocumentTreeNodeData>()
79+
.SelectMany(n => n.GetModule()?.Assembly.Modules ?? [])
80+
.Distinct();
81+
}
82+
}
83+
84+
[ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:FindStringReferencesInModuleCommand", Icon = DsImagesAttribute.Search, Group = MenuConstants.GROUP_APP_MENU_EDIT_FIND, Order = 20)]
85+
sealed class MenuBarCommand : FindStringReferencesInModuleCommand {
86+
private readonly IDocumentTabService documentTabService;
87+
88+
[ImportingConstructor]
89+
public MenuBarCommand(IDsToolWindowService toolWindowService, IStringReferencesService stringReferencesService, IDocumentTabService documentTabService)
90+
: base(toolWindowService, stringReferencesService) {
91+
this.documentTabService = documentTabService;
92+
}
93+
94+
protected override IEnumerable<ModuleDef> GetModules(IMenuItemContext context) {
95+
return documentTabService.DocumentTreeView.TreeView.SelectedItems
96+
.OfType<DocumentTreeNodeData>()
97+
.SelectMany(n => n.GetModule()?.Assembly.Modules ?? [])
98+
.Distinct();
99+
}
100+
}
101+
}
102+
103+
abstract class ReferenceCommandBase(Lazy<IStringReferencesService> service) : MenuItemBase {
104+
public Lazy<IStringReferencesService> Service { get; } = service;
105+
106+
public override void Execute(IMenuItemContext context) {
107+
var reference = GetReference(context);
108+
if (reference is null)
109+
return;
110+
Execute(context, reference);
111+
}
112+
113+
protected abstract void Execute(IMenuItemContext context, StringReference reference);
114+
115+
public override bool IsVisible(IMenuItemContext context) => GetReference(context) is not null;
116+
117+
private static StringReference? GetReference(IMenuItemContext context) {
118+
if (context.CreatorObject.Guid != new Guid(StringSearcherConstants.GUID_STRINGS_LISTBOX))
119+
return null;
120+
121+
return context.Find<StringReference>();
122+
}
123+
}
124+
125+
[ExportMenuItem(Header = "res:CopyStringLiteralCommand", Group = StringSearcherConstants.GUID_CTX_GROUP_COPY, Order = 0, Icon = DsImagesAttribute.Copy, InputGestureText = "res:ShortCutKeyCtrlC")]
126+
sealed class CopyStringLiteralCommand : ReferenceCommandBase {
127+
[ImportingConstructor]
128+
CopyStringLiteralCommand(Lazy<IStringReferencesService> service)
129+
: base(service) {
130+
}
131+
132+
protected override void Execute(IMenuItemContext context, StringReference reference) => ExecuteInternal(reference);
133+
134+
internal static void ExecuteInternal(StringReference? reference) {
135+
if (reference is null) {
136+
return;
137+
}
138+
139+
try {
140+
Clipboard.SetText(reference.FormattedLiteral);
141+
}
142+
catch (ExternalException) { }
143+
}
144+
}
145+
146+
[ExportMenuItem(Header = "res:CopyRawStringLiteralCommand", Group = StringSearcherConstants.GUID_CTX_GROUP_COPY, Order = 1)]
147+
sealed class CopyRawStringLiteralCommand : ReferenceCommandBase {
148+
[ImportingConstructor]
149+
CopyRawStringLiteralCommand(Lazy<IStringReferencesService> service)
150+
: base(service) {
151+
}
152+
153+
protected override void Execute(IMenuItemContext context, StringReference reference) => ExecuteInternal(reference);
154+
155+
internal static void ExecuteInternal(StringReference? reference) {
156+
if (reference is null) {
157+
return;
158+
}
159+
160+
try {
161+
Clipboard.SetText(reference.Literal, TextDataFormat.UnicodeText);
162+
}
163+
catch (ExternalException) { }
164+
}
165+
}
166+
167+
abstract class OpenReferenceCommandBase : ReferenceCommandBase {
168+
readonly bool newTab;
169+
170+
protected OpenReferenceCommandBase(Lazy<IStringReferencesService> service, bool newTab)
171+
: base(service) {
172+
this.newTab = newTab;
173+
}
174+
175+
protected override void Execute(IMenuItemContext context, StringReference reference) => Service.Value.FollowReference(reference, newTab);
176+
}
177+
178+
[ExportMenuItem(Header = "res:GoToReferenceInCodeCommand", InputGestureText = "res:DoubleClick", Group = StringSearcherConstants.GUID_CTX_GROUP_FOLLOW, Order = 0)]
179+
sealed class OpenReferenceCommand : OpenReferenceCommandBase {
180+
[ImportingConstructor]
181+
OpenReferenceCommand(Lazy<IStringReferencesService> service)
182+
: base(service, false) {
183+
}
184+
}
185+
186+
[ExportMenuItem(Header = "res:GoToReferenceInCodeNewTabCommand", Group = StringSearcherConstants.GUID_CTX_GROUP_FOLLOW, Order = 1)]
187+
sealed class OpenReferenceNewTabCommand : OpenReferenceCommandBase {
188+
[ImportingConstructor]
189+
OpenReferenceNewTabCommand(Lazy<IStringReferencesService> service)
190+
: base(service, true) {
191+
}
192+
}
193+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
Copyright (C) 2025 Washi
3+
4+
This file is part of dnSpy
5+
6+
dnSpy is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
dnSpy is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU General Public License for more details.
15+
16+
You should have received a copy of the GNU General Public License
17+
along with dnSpy. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
20+
using System;
21+
using dnlib.DotNet;
22+
using dnSpy.Contracts.Text.Classification;
23+
24+
namespace dnSpy.StringSearcher {
25+
public sealed class ConstantStringReference(StringReferenceContext context, string literal, IHasConstant referrer)
26+
: StringReference(context, literal, referrer) {
27+
28+
public new IHasConstant Referrer => (IHasConstant)base.Referrer;
29+
30+
public IMemberDef Container => Referrer switch {
31+
FieldDef or PropertyDef => (IMemberDef)Referrer,
32+
ParamDef param => param.DeclaringMethod,
33+
_ => throw new ArgumentOutOfRangeException(nameof(Referrer)),
34+
};
35+
36+
public override StringReferenceKind Kind => StringReferenceKind.Constant;
37+
38+
public override ModuleDef Module => Container.Module;
39+
40+
public override MDToken Token => Referrer.MDToken;
41+
42+
public override object ContainerObject => Container;
43+
44+
protected override void WriteReferrerUI(TextClassifierTextColorWriter writer) {
45+
Context.Decompiler.Write(writer, Container, DefaultFormatterOptions);
46+
47+
if (Referrer is ParamDef param) {
48+
WriteParameterReference(writer, param);
49+
}
50+
}
51+
}
52+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
Copyright (C) 2025 Washi
3+
4+
This file is part of dnSpy
5+
6+
dnSpy is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
dnSpy is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU General Public License for more details.
15+
16+
You should have received a copy of the GNU General Public License
17+
along with dnSpy. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
20+
using dnlib.DotNet;
21+
using dnSpy.Contracts.Documents.TreeView;
22+
using dnSpy.Contracts.Text;
23+
using dnSpy.Contracts.Text.Classification;
24+
using dnSpy.StringSearcher.Properties;
25+
26+
namespace dnSpy.StringSearcher {
27+
public sealed class CustomAttributeStringReference(
28+
StringReferenceContext context,
29+
string literal,
30+
IHasCustomAttribute referrer,
31+
IMDTokenProvider container,
32+
ModuleDef module,
33+
CustomAttribute attribute,
34+
string argumentName)
35+
: StringReference(context, literal, referrer) {
36+
37+
public CustomAttribute CustomAttribute { get; } = attribute;
38+
39+
public override StringReferenceKind Kind => StringReferenceKind.Attribute;
40+
41+
public override ModuleDef Module => module;
42+
43+
public override MDToken Token => ((IMDTokenProvider)ContainerObject).MDToken;
44+
45+
public override object ContainerObject => container;
46+
47+
protected override void WriteReferrerUI(TextClassifierTextColorWriter writer) {
48+
switch (ContainerObject) {
49+
case ModuleDef module:
50+
writer.WriteModule(module.Name);
51+
break;
52+
case AssemblyDef assembly:
53+
new NodeFormatter().Write(writer, Context.Decompiler, assembly, false, true, true);
54+
break;
55+
case IMemberRef memberRef:
56+
Context.Decompiler.Write(writer, memberRef, DefaultFormatterOptions);
57+
break;
58+
default:
59+
writer.Write(ContainerObject.ToString()!);
60+
break;
61+
}
62+
63+
switch (Referrer) {
64+
case ParamDef param:
65+
WriteParameterReference(writer, param);
66+
break;
67+
case GenericParam param:
68+
writer.Write(" ");
69+
writer.Write(TextColor.DarkGray, dnSpy_StringSearcher_Resources.ReferrerGenericParameter);
70+
writer.Write(" ");
71+
Context.Decompiler.Write(writer, param, DefaultFormatterOptions);
72+
break;
73+
}
74+
75+
writer.Write(" ");
76+
writer.Write(TextColor.DarkGray, dnSpy_StringSearcher_Resources.ReferrerAttribute);
77+
writer.Write(" ");
78+
Context.Decompiler.Write(writer, CustomAttribute.AttributeType, DefaultFormatterOptions);
79+
writer.Write(TextColor.Punctuation, " (");
80+
writer.Write(TextColor.InstanceProperty, argumentName);
81+
writer.Write(TextColor.Punctuation, ")");
82+
}
83+
}
84+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
Copyright (C) 2025 Washi
3+
4+
This file is part of dnSpy
5+
6+
dnSpy is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
dnSpy is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU General Public License for more details.
15+
16+
You should have received a copy of the GNU General Public License
17+
along with dnSpy. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
20+
using dnlib.DotNet;
21+
using dnSpy.Contracts.Text;
22+
using dnSpy.Contracts.Text.Classification;
23+
24+
namespace dnSpy.StringSearcher {
25+
public sealed class ILStringReference(StringReferenceContext context, string literal, MethodDef referrer, uint offset)
26+
: StringReference(context, literal, referrer) {
27+
28+
public new MethodDef Referrer => (MethodDef)base.Referrer;
29+
30+
public override StringReferenceKind Kind => StringReferenceKind.IL;
31+
32+
public override ModuleDef Module => Referrer.Module;
33+
34+
public override MDToken Token => Referrer.MDToken;
35+
36+
public uint Offset { get; } = offset;
37+
38+
public override object ContainerObject => Referrer;
39+
40+
protected override void WriteReferrerUI(TextClassifierTextColorWriter writer) {
41+
Context.Decompiler.Write(writer, Referrer, DefaultFormatterOptions);
42+
writer.Write(TextColor.Punctuation, "+");
43+
writer.Write(TextColor.Label, $"IL_{Offset:X4}");
44+
}
45+
}
46+
}

0 commit comments

Comments
 (0)