Skip to content

Instantly share code, notes, and snippets.

@xoofx
Last active June 6, 2019 13:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xoofx/95d1b2a7343611891f09a951813038ef to your computer and use it in GitHub Desktop.
Save xoofx/95d1b2a7343611891f09a951813038ef to your computer and use it in GitHub Desktop.
List usage of non public Method/Field/Type members from a specified assembly to any external assembly references
using System;
using System.Collections.Generic;
using System.IO;
using CecilTool;
using Mono.Cecil;
namespace CecilTool
{
/// <summary>
/// Simple tool using Mono.Cecil to dump the list of usage of internals methods/fields
/// referencing another assembly from a specified assembly.
///
/// Usage: ApiVerifier.DumpUsageOfInternalsFromExternalAssemblies(@"C:\Path\To\My\Assembly.dll")
///
/// </summary>
public static class ApiVerifier
{
public static void DumpUsageOfInternalsFromExternalAssemblies(string assemblyPath)
{
if (assemblyPath == null) throw new ArgumentNullException(nameof(assemblyPath));
DumpUsageOfInternalsFromExternalAssemblies(new List<string>() {assemblyPath});
}
public static void DumpUsageOfInternalsFromExternalAssemblies(List<string> assemblyPaths)
{
if (assemblyPaths == null) throw new ArgumentNullException(nameof(assemblyPaths));
var assemblyFullPaths = new List<string>();
var assemblyResolver = new DefaultAssemblyResolver();
foreach (var assemblyPath in assemblyPaths)
{
var fullPath = Path.Combine(Environment.CurrentDirectory, assemblyPath);
assemblyFullPaths.Add(fullPath);
assemblyResolver.AddSearchDirectory(Path.GetDirectoryName(fullPath));
}
for (var i = 0; i < assemblyFullPaths.Count; i++)
{
var fullPath = assemblyFullPaths[i];
var assembly = AssemblyDefinition.ReadAssembly(fullPath, new ReaderParameters(ReadingMode.Immediate)
{
AssemblyResolver = assemblyResolver
});
if (i > 0) Console.WriteLine();
DumpUsageOfInternalsFromExternalAssemblies(assembly);
}
}
public static void DumpUsageOfInternalsFromExternalAssemblies(AssemblyDefinition assembly)
{
if (assembly == null) throw new ArgumentNullException(nameof(assembly));
const int mdCountMax = 1 << 24;
bool dumpAssemblyName = true;
foreach (var module in assembly.Modules)
{
for (int i = 1; i < mdCountMax; i++)
{
var item = module.LookupToken(new MetadataToken(TokenType.MemberRef, i));
if (item == null)
{
break;
}
if (item is MemberReference memberRef)
{
var memberDef = memberRef.Resolve();
bool isInternal = CheckIsInternal(memberDef);
if (isInternal && !assembly.Modules.Contains(memberDef.DeclaringType.Module))
{
if (dumpAssemblyName)
{
Console.WriteLine($"Internals used in Assembly: {assembly.Name} ({assembly.MainModule.FileName})");
Console.WriteLine("---------------------------------------------------------------------------------------");
dumpAssemblyName = false;
}
Console.WriteLine($"{memberDef.GetType().Name}: {memberRef} => Assembly: {memberDef.DeclaringType.Module.FileName}");
}
}
}
}
}
private static bool CheckIsInternal(IMemberDefinition memberDef)
{
if (memberDef == null)
{
return false;
}
bool isInternal = false;
switch (memberDef)
{
case MethodDefinition methodDef:
isInternal = methodDef.IsAssembly;
break;
case FieldDefinition fieldDef:
isInternal = fieldDef.IsAssembly;
break;
case TypeDefinition typeDef:
isInternal = !(typeDef.IsPublic || typeDef.IsNestedPublic);
break;
}
// Check also the declaring type
return isInternal || CheckIsInternal(memberDef.DeclaringType);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment