mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
322 lines
11 KiB
322 lines
11 KiB
// 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.Linq; |
|
using System.Reflection; |
|
using System.Threading; |
|
using ICSharpCode.Decompiler; |
|
using ICSharpCode.Decompiler.Dom; |
|
using ICSharpCode.Decompiler.TypeSystem.Implementation; |
|
using ICSharpCode.Decompiler.Util; |
|
|
|
namespace ICSharpCode.ILSpy.TreeNodes.Analyzer |
|
{ |
|
/// <summary> |
|
/// Determines the accessibility domain of a member for where-used analysis. |
|
/// </summary> |
|
internal class ScopedWhereUsedAnalyzer<T> |
|
{ |
|
private readonly PEFile assemblyScope; |
|
private TypeDefinition typeScope; |
|
private static readonly TypeSystemAttributeTypeProvider typeProvider = TypeSystemAttributeTypeProvider.CreateDefault(); |
|
|
|
private readonly Accessibility memberAccessibility = Accessibility.Public; |
|
private Accessibility typeAccessibility = Accessibility.Public; |
|
private readonly Func<TypeDefinition, IEnumerable<T>> typeAnalysisFunction; |
|
|
|
public ScopedWhereUsedAnalyzer(TypeDefinition type, Func<TypeDefinition, IEnumerable<T>> typeAnalysisFunction) |
|
{ |
|
this.typeScope = type; |
|
this.assemblyScope = type.Module; |
|
this.typeAnalysisFunction = typeAnalysisFunction; |
|
} |
|
|
|
public ScopedWhereUsedAnalyzer(MethodDefinition method, Func<TypeDefinition, IEnumerable<T>> typeAnalysisFunction) |
|
: this(method.DeclaringType, typeAnalysisFunction) |
|
{ |
|
this.memberAccessibility = GetMethodAccessibility(method); |
|
} |
|
|
|
public ScopedWhereUsedAnalyzer(PropertyDefinition property, Func<TypeDefinition, IEnumerable<T>> typeAnalysisFunction) |
|
: this(property.DeclaringType, typeAnalysisFunction) |
|
{ |
|
Accessibility getterAccessibility = (property.GetMethod.IsNil) ? Accessibility.Private : GetMethodAccessibility(property.GetMethod); |
|
Accessibility setterAccessibility = (property.SetMethod.IsNil) ? Accessibility.Private : GetMethodAccessibility(property.SetMethod); |
|
this.memberAccessibility = (Accessibility)Math.Max((int)getterAccessibility, (int)setterAccessibility); |
|
} |
|
|
|
public ScopedWhereUsedAnalyzer(EventDefinition eventDef, Func<TypeDefinition, IEnumerable<T>> typeAnalysisFunction) |
|
: this(eventDef.DeclaringType, typeAnalysisFunction) |
|
{ |
|
// we only have to check the accessibility of the the get method |
|
// [CLS Rule 30: The accessibility of an event and of its accessors shall be identical.] |
|
this.memberAccessibility = GetMethodAccessibility(eventDef.AddMethod); |
|
} |
|
|
|
public ScopedWhereUsedAnalyzer(FieldDefinition field, Func<TypeDefinition, IEnumerable<T>> typeAnalysisFunction) |
|
: this(field.DeclaringType, typeAnalysisFunction) |
|
{ |
|
switch (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.Family: |
|
memberAccessibility = Accessibility.Family; |
|
break; |
|
case FieldAttributes.FamORAssem: |
|
memberAccessibility = Accessibility.FamilyOrInternal; |
|
break; |
|
case FieldAttributes.Public: |
|
memberAccessibility = Accessibility.Public; |
|
break; |
|
} |
|
} |
|
|
|
private Accessibility GetMethodAccessibility(MethodDefinition method) |
|
{ |
|
Accessibility accessibility; |
|
switch (method.Attributes & MethodAttributes.MemberAccessMask) { |
|
case MethodAttributes.Private: |
|
default: |
|
accessibility = Accessibility.Private; |
|
break; |
|
case MethodAttributes.FamANDAssem: |
|
accessibility = Accessibility.FamilyAndInternal; |
|
break; |
|
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); |
|
} |
|
|
|
private void DetermineTypeAccessibility() |
|
{ |
|
while (!typeScope.DeclaringType.IsNil) { |
|
Accessibility accessibility = GetNestedTypeAccessibility(typeScope); |
|
if ((int)typeAccessibility > (int)accessibility) { |
|
typeAccessibility = accessibility; |
|
if (typeAccessibility == Accessibility.Private) |
|
return; |
|
} |
|
typeScope = typeScope.DeclaringType; |
|
} |
|
|
|
if (typeScope.IsNotPublic && |
|
((int)typeAccessibility > (int)Accessibility.Internal)) { |
|
typeAccessibility = Accessibility.Internal; |
|
} |
|
} |
|
|
|
private static Accessibility GetNestedTypeAccessibility(TypeDefinition type) |
|
{ |
|
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> |
|
private enum Accessibility |
|
{ |
|
Private, |
|
FamilyAndInternal, |
|
Internal, |
|
Family, |
|
FamilyOrInternal, |
|
Public |
|
} |
|
|
|
private IEnumerable<T> FindReferencesInAssemblyAndFriends(CancellationToken ct) |
|
{ |
|
var assemblies = GetAssemblyAndAnyFriends(assemblyScope, ct); |
|
|
|
// use parallelism only on the assembly level (avoid locks within Cecil) |
|
return assemblies.AsParallel().WithCancellation(ct).SelectMany(a => FindReferencesInAssembly(a, ct)); |
|
} |
|
|
|
private IEnumerable<T> FindReferencesGlobal(CancellationToken ct) |
|
{ |
|
var assemblies = GetReferencingAssemblies(assemblyScope, ct); |
|
|
|
// use parallelism only on the assembly level (avoid locks within Cecil) |
|
return assemblies.AsParallel().WithCancellation(ct).SelectMany(asm => FindReferencesInAssembly(asm, ct)); |
|
} |
|
|
|
private IEnumerable<T> FindReferencesInAssembly(PEFile asm, CancellationToken ct) |
|
{ |
|
foreach (TypeDefinition type in TreeTraversal.PreOrder(asm.TypeDefinitions, t => t.NestedTypes)) { |
|
ct.ThrowIfCancellationRequested(); |
|
foreach (var result in typeAnalysisFunction(type)) { |
|
ct.ThrowIfCancellationRequested(); |
|
yield return result; |
|
} |
|
} |
|
} |
|
|
|
private IEnumerable<T> FindReferencesInTypeScope(CancellationToken ct) |
|
{ |
|
foreach (TypeDefinition type in TreeTraversal.PreOrder(typeScope, t => t.NestedTypes)) { |
|
ct.ThrowIfCancellationRequested(); |
|
foreach (var result in typeAnalysisFunction(type)) { |
|
ct.ThrowIfCancellationRequested(); |
|
yield return result; |
|
} |
|
} |
|
} |
|
|
|
private IEnumerable<T> FindReferencesInEnclosingTypeScope(CancellationToken ct) |
|
{ |
|
foreach (TypeDefinition type in TreeTraversal.PreOrder(typeScope.DeclaringType, t => t.NestedTypes)) { |
|
ct.ThrowIfCancellationRequested(); |
|
foreach (var result in typeAnalysisFunction(type)) { |
|
ct.ThrowIfCancellationRequested(); |
|
yield return result; |
|
} |
|
} |
|
} |
|
|
|
private IEnumerable<PEFile> GetReferencingAssemblies(PEFile asm, CancellationToken ct) |
|
{ |
|
yield return asm; |
|
|
|
string requiredAssemblyFullName = asm.FullName; |
|
|
|
IEnumerable<LoadedAssembly> assemblies = MainWindow.Instance.CurrentAssemblyList.GetAssemblies().Where(assy => assy.GetPEFileOrNull()?.IsAssembly == true); |
|
|
|
foreach (var assembly in assemblies) { |
|
ct.ThrowIfCancellationRequested(); |
|
bool found = false; |
|
var module = assembly.GetPEFileOrNull(); |
|
if (module == null) |
|
continue; |
|
var metadata = module.GetMetadataReader(); |
|
foreach (var reference in module.AssemblyReferences) { |
|
if (requiredAssemblyFullName == reference.FullName) { |
|
found = true; |
|
break; |
|
} |
|
} |
|
if (found && AssemblyReferencesScopeType(module)) |
|
yield return module; |
|
} |
|
} |
|
|
|
private IEnumerable<PEFile> GetAssemblyAndAnyFriends(PEFile asm, CancellationToken ct) |
|
{ |
|
yield return asm; |
|
var reader = asm.GetMetadataReader(); |
|
|
|
var attributes = reader.CustomAttributes.Select(h => reader.GetCustomAttribute(h)).Where(ca => ca.GetAttributeType(asm).FullName.ToString() == "System.Runtime.CompilerServices.InternalsVisibleToAttribute"); |
|
var friendAssemblies = new HashSet<string>(); |
|
foreach (var attribute in attributes) { |
|
string assemblyName = attribute.DecodeValue(typeProvider).FixedArguments[0].Value as string; |
|
assemblyName = assemblyName.Split(',')[0]; // strip off any public key info |
|
friendAssemblies.Add(assemblyName); |
|
} |
|
|
|
if (friendAssemblies.Count > 0) { |
|
IEnumerable<LoadedAssembly> assemblies = MainWindow.Instance.CurrentAssemblyList.GetAssemblies(); |
|
|
|
foreach (var assembly in assemblies) { |
|
ct.ThrowIfCancellationRequested(); |
|
if (friendAssemblies.Contains(assembly.ShortName)) { |
|
var module = assembly.GetPEFileOrNull(); |
|
if (module == null) |
|
continue; |
|
if (AssemblyReferencesScopeType(module)) |
|
yield return module; |
|
} |
|
} |
|
} |
|
} |
|
|
|
private bool AssemblyReferencesScopeType(PEFile asm) |
|
{ |
|
bool hasRef = false; |
|
foreach (var typeRef in asm.TypeReferences) { |
|
if (typeRef.Name == typeScope.Name && typeRef.Namespace == typeScope.Namespace) { |
|
hasRef = true; |
|
break; |
|
} |
|
} |
|
return hasRef; |
|
} |
|
} |
|
}
|
|
|