Dnspy/Extensions/dnSpy.Analyzer/TreeNodes/ScopedWhereUsedAnalyzer.cs
2021-09-20 18:20:01 +02:00

342 lines
13 KiB
C#

// 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,
/// <summary>
/// Search all modules, eg. used if it's a type opted in to type equivalence, or DllImport methods, or COM types/members.
/// </summary>
IncludeAllModules = 0x00000001,
/// <summary>
/// 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.
/// </summary>
ForcePublic = 0x00000002,
}
/// <summary>
/// Determines the accessibility domain of a member for where-used analysis.
/// </summary>
sealed class ScopedWhereUsedAnalyzer<T> {
readonly IDsDocumentService documentService;
readonly TypeDef analyzedType;
readonly List<ModuleDef> allModules;
readonly ScopedWhereUsedAnalyzerOptions options;
TypeDef typeScope;
internal List<ModuleDef> AllModules => allModules;
readonly Accessibility memberAccessibility = Accessibility.Public;
Accessibility typeAccessibility = Accessibility.Public;
readonly Func<TypeDef, IEnumerable<T>> typeAnalysisFunction;
public ScopedWhereUsedAnalyzer(IDsDocumentService documentService, TypeDef analyzedType, Func<TypeDef, IEnumerable<T>> typeAnalysisFunction, ScopedWhereUsedAnalyzerOptions options = ScopedWhereUsedAnalyzerOptions.None) {
this.analyzedType = analyzedType;
typeScope = analyzedType;
this.typeAnalysisFunction = typeAnalysisFunction;
this.documentService = documentService;
allModules = new List<ModuleDef>();
this.options = options;
}
public ScopedWhereUsedAnalyzer(IDsDocumentService documentService, MethodDef method, Func<TypeDef, IEnumerable<T>> typeAnalysisFunction, ScopedWhereUsedAnalyzerOptions options = ScopedWhereUsedAnalyzerOptions.None)
: this(documentService, method.DeclaringType, typeAnalysisFunction, options) => memberAccessibility = GetMethodAccessibility(method, options);
public ScopedWhereUsedAnalyzer(IDsDocumentService documentService, PropertyDef property, Func<TypeDef, IEnumerable<T>> 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<TypeDef, IEnumerable<T>> 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<TypeDef, IEnumerable<T>> 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<T> 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;
}
/// <summary>
/// The effective accessibility of a member
/// </summary>
enum Accessibility {
Private,
FamilyAndInternal,
Internal,
Family,
FamilyOrInternal,
Public
}
IEnumerable<T> FindReferencesInAssemblyAndFriends(CancellationToken ct) {
IEnumerable<ModuleDef> modules;
if ((options & ScopedWhereUsedAnalyzerOptions.IncludeAllModules) != 0)
modules = documentService.GetDocuments().Select(a => a.ModuleDef).OfType<ModuleDef>();
else if (TIAHelper.IsTypeDefEquivalent(analyzedType)) {
var analyzedTypes = new List<TypeDef> { 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<T> FindReferencesGlobal(CancellationToken ct) {
IEnumerable<ModuleDef> modules;
if ((options & ScopedWhereUsedAnalyzerOptions.IncludeAllModules) != 0)
modules = documentService.GetDocuments().Select(a => a.ModuleDef).OfType<ModuleDef>();
else if (TIAHelper.IsTypeDefEquivalent(analyzedType)) {
var analyzedTypes = new List<TypeDef> { 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<T> 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<T> 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<T> 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<ModuleDef> 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<ModuleDef> 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;
}
}
}