// Copyright Keysight Technologies 2012-2019
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at http://mozilla.org/MPL/2.0/.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using Tap.Shared;
[assembly: OpenTap.PluginAssembly(true)]
namespace OpenTap
{
///
/// Marks an assembly as one containing OpenTAP plugins.
///
[AttributeUsage(AttributeTargets.Assembly)]
public class PluginAssemblyAttribute : Attribute
{
///
/// Ask the to also look for plugins among the internal types in this assembly (default is to only search in public types).
///
public bool SearchInternalTypes { get; }
///
/// (Optional) Full name of Plugin Init method that gets run before any other code in the plugin. Will only run once.
/// Requirement: Must be parameterless public static method returning void inside public static class
/// Important note: If init method fails (throws an ), then NONE of the types will load
///
public string PluginInitMethod { get; }
///
/// Marks an assembly as one containing OpenTAP plugins.
///
/// True to ask the to also look for plugins among the internal types in this assembly (default is to only search in public types).
public PluginAssemblyAttribute(bool SearchInternalTypes)
{
this.SearchInternalTypes = SearchInternalTypes;
}
///
/// Marks an assembly as one containing OpenTAP plugins.
///
/// True to ask the to also look for plugins among the internal types in this assembly (default is to only search in public types).
/// Full name of Plugin Init method ()
public PluginAssemblyAttribute(bool SearchInternalTypes, string PluginInitMethod)
{
this.SearchInternalTypes = SearchInternalTypes;
this.PluginInitMethod = PluginInitMethod;
}
}
///
/// Searches assemblies for classes implementing ITapPlugin.
///
public class PluginSearcher
{
private Options Option { get; set; }
///
/// Options for Plugin Searcher.
///
[Flags]
public enum Options
{
/// No options
None = 0,
/// Allow multiple assemblies with the same name
IncludeSameAssemblies = 1
}
///
/// Searches assemblies for classes implementing ITapPlugin.
///
public PluginSearcher() { }
internal PluginSearcher(PluginSearcher copy)
{
AbsorbLoadedPlugins(copy);
if (AllTypes.TryGetValue(PluginMarkerType.Name, out var marker)) PluginMarkerType = marker;
}
///
/// Searches assemblies for classes implementing ITapPlugin.
///
/// Option setting for Plugin Searcher.
public PluginSearcher(Options opts = Options.None)
{
Option = opts;
}
private class AssemblyRef
{
public string Name;
public Version Version;
public AssemblyRef(string name, Version version)
{
Name = name;
Version = version;
}
public override int GetHashCode()
{
return Name.GetHashCode() * 17 + Version.GetHashCode();
}
public override bool Equals(object obj)
{
if (obj is AssemblyRef)
{
var o = obj as AssemblyRef;
return (Name==o.Name) && (Version==o.Version);
}
return base.Equals(obj);
}
}
private static readonly TraceSource log = Log.CreateSource("Searcher");
class AssemblyDependencyGraph
{
private Options Option { get; set; }
public AssemblyDependencyGraph(Options opt)
{
nameToAsmMap = new Dictionary();
nameToAsmMap2 = new Dictionary();
asmNameToAsmData = new Dictionary();
Assemblies = new List();
UnfoundAssemblies = new HashSet();
Option = opt;
}
static string VersionFromFileVersion(FileVersionInfo v)
{
return $"{v.FileMajorPart}.{v.FileMinorPart}.{v.FileBuildPart}";
}
///
/// Returns a list of assemblies and their dependencies/references.
/// The list is sorted such that a dependency is before the assembly/assemblies that depend on it.
///
public List Generate(IEnumerable files)
{
if (nameToFileMap == null)
nameToFileMap = files.ToLookup(Path.GetFileNameWithoutExtension);
else
{
var existingFiles = nameToFileMap.SelectMany(g => g.Select(s => s));
nameToFileMap = existingFiles.Concat(files).Distinct().ToLookup(Path.GetFileNameWithoutExtension);
}
// print a warning if the same assembly is loaded more than once.
foreach (var entry in nameToFileMap)
{
var count = entry.Count();
if (count == 1) continue;
if (entry.Key.EndsWith(".resources") && entry.Key.StartsWith("Microsoft.CodeAnalysis"))
continue; // This improves the performance in debug builds, where lots of locale resource files are present.
var versions = new HashSet();
bool allInDependencies = true;
foreach (var file in entry)
{
try
{
if ((Path.GetDirectoryName(file)?.Contains("Dependencies") ?? false) == false)
allInDependencies = false;
var fileVersion = FileVersionInfo.GetVersionInfo(file);
// According to docs fileVersion is never null. Could be set to 0.0.0 though, but this is fine.
versions.Add(VersionFromFileVersion(fileVersion));
}
catch
{
// Accept errors here, this code is only used to print warnings.
}
}
if (allInDependencies) continue; // these were only inside the dependencies folder.
if (versions.Count == 1) continue;
log.Warning("Multiple assemblies of different versions named {0} exists ", entry.Key);
int i = 0;
foreach (var file in entry)
{
string ver = "unknown";
try
{
ver = VersionFromFileVersion(FileVersionInfo.GetVersionInfo(file));
}
catch (Exception)
{
log.Debug("Unable to get version of {0}.", file);
}
log.Debug("Assembly {2}: {0} version: {1}", file, ver, 1 + i++);
}
}
foreach (string file in files)
AddAssemblyInfo(file);
return Assemblies;
}
private List Assemblies;
private Dictionary nameToAsmMap;
private Dictionary nameToAsmMap2;
private readonly Dictionary asmNameToAsmData;
private ILookup nameToFileMap;
HashSet UnfoundAssemblies; // for assemblies that are not in the files.
ImmutableDictionary assemblyToAssemblyDataLookup = ImmutableDictionary.Empty;
///
/// Find the AssemblyData for a specific loaded Assembly. If not found, it will analyze it and cache it.
///
internal AssemblyData FindAssemblyData(Assembly asm)
{
if (!assemblyToAssemblyDataLookup.TryGetValue(asm, out var asmData))
{
var name = asm.FullName;
if (asmNameToAsmData.TryGetValue(name, out var asmData2))
{
asmData = asmData2;
}else if (nameToAsmMap2.TryGetValue(asm.Location?.ToUpper() ?? "", out var asmRef) && nameToAsmMap.TryGetValue(asmRef, out var asmData3))
{
asmData = asmData3;
}
if (asmData == null)
{
foreach (var assembly in Assemblies)
{
if (assembly.GetCached() == asm)
{
asmData = assembly;
break;
}
}
}
if (asmData == null)
{
asmData = AddAssemblyInfo(asm.Location, asm);
}
assemblyToAssemblyDataLookup = assemblyToAssemblyDataLookup.Add(asm, asmData);
}
return asmData;
}
/// Manually analyze and add an assembly file.
internal AssemblyData AddAssemblyInfo(string file, Assembly loadedAssembly = null)
{
var normalizedFile = PathUtils.NormalizePath(file);
if (nameToAsmMap2.TryGetValue(normalizedFile, out AssemblyRef asmRef2))
{
return nameToAsmMap[asmRef2];
}
try
{
// This can happen if the plugin was uninstalled.
// If the plugin was loaded, it was most likely also searched if we reached this point.
if (!File.Exists(file)) return null;
if (file.Contains(".resources.dll"))
return null;
var thisAssembly = new AssemblyData(file, loadedAssembly);
List refNames = new List();
using (FileStream str = new FileStream(file, FileMode.Open, FileAccess.Read))
{
if(str.Length > int.MaxValue)
return null; // otherwise PEReader() will throw.
if(str.Length < 50)
return null; // Don't consider super small assemblies.
using (PEReader header = new PEReader(str, PEStreamOptions.LeaveOpen))
{
if (!header.HasMetadata)
return null;
MetadataReader metadata = header.GetMetadataReader();
AssemblyDefinition def = metadata.GetAssemblyDefinition();
// if we were asked to only provide distinct assembly names and
// this assembly name has already been encountered, just return that.
var fileIdentifier = Option.HasFlag(Options.IncludeSameAssemblies) ? file : def.GetAssemblyName().FullName;
if (asmNameToAsmData.TryGetValue(fileIdentifier, out AssemblyData data))
return data;
thisAssembly.Name = metadata.GetString(def.Name);
if (string.Compare(thisAssembly.Name, Path.GetFileNameWithoutExtension(file), true) != 0)
throw new Exception("Assembly name does not match the file name.");
var thisRef = new AssemblyRef(thisAssembly.Name, def.Version);
var prov = new CustomAttributeTypeProvider();
foreach (CustomAttributeHandle attrHandle in def.GetCustomAttributes())
{
CustomAttribute attr = metadata.GetCustomAttribute(attrHandle);
if (attr.Constructor.Kind == HandleKind.MemberReference)
{
var ctor = metadata.GetMemberReference((MemberReferenceHandle)attr.Constructor);
string attributeFullName = GetFullName(metadata, ctor.Parent);
if (attributeFullName == typeof(AssemblyInformationalVersionAttribute).FullName)
{
var valueString = attr.DecodeValue(prov).FixedArguments[0].Value?.ToString();
if (SemanticVersion.TryParse(valueString, out _))
thisAssembly.RawVersion = valueString;
break;
}
}
}
// If the semantic version was not set, fall back to using the version
// from the AssemblyDefinition
if (string.IsNullOrWhiteSpace(thisAssembly.RawVersion))
{
thisAssembly.RawVersion = def.Version.ToString();
}
thisAssembly.Version = def.Version;
if (!nameToAsmMap.ContainsKey(thisRef))
{
nameToAsmMap.Add(thisRef, thisAssembly);
nameToAsmMap2[PathUtils.NormalizePath(thisAssembly.Location)] = thisRef;
}
asmNameToAsmData[fileIdentifier] = thisAssembly;
foreach (var asmRefHandle in metadata.AssemblyReferences)
{
var asmRef = metadata.GetAssemblyReference(asmRefHandle);
var name = metadata.GetString(asmRef.Name);
var newRef = new AssemblyRef(name, asmRef.Version);
if (UnfoundAssemblies.Contains(newRef))
{
continue;
}
refNames.Add(new AssemblyRef(name, asmRef.Version));
}
}
List refList = null;
foreach (var refName in refNames)
{
if (nameToAsmMap.TryGetValue(refName, out AssemblyData asmData2))
{
if (refList == null) refList = new List();
refList.Add(asmData2);
}
else
{
if (nameToFileMap.Contains(refName.Name))
{
AssemblyData asm = null;
foreach (string file2 in nameToFileMap[refName.Name])
{
var data = AddAssemblyInfo(file2);
if (data == null) continue;
if (data.Version == refName.Version)
{
asm = data;
break;
}
else if (Utils.Compatible(data.Version, refName.Version))
{
asm = data;
}
}
if (asm != null)
{
if (refList == null) refList = new List();
refList.Add(asm);
}
else
{
UnfoundAssemblies.Add(refName);
}
}
else
{
UnfoundAssemblies.Add(refName);
}
}
}
thisAssembly.References = (IEnumerable) refList ?? Array.Empty();
Assemblies.Add(thisAssembly);
return thisAssembly;
}
}
catch (Exception ex)
{
// there was an error loading the file. Ignore that file.
log.Warning("Skipping assembly '{0}'. {1}", Path.GetFileName(file), ex.Message);
log.Debug(ex);
return null;
}
}
}
///
/// The assemblies found by Search. Ordered such that referenced assemblies come before assemblies that reference them.
///
public IEnumerable Assemblies;
AssemblyDependencyGraph graph = null;
///
/// Searches assembly files and returns all the plugin types found in those.
/// The search will also populate a complete list of types searched in the AllTypes property
/// and all Assemblies found in the Assemblies property.
/// Subsequent calls to this method will add to those properties.
///
public IEnumerable Search(string dir)
{
var finder = new AssemblyFinder() { Quiet = true, IncludeDependencies = true, DirectoriesToSearch = new[] { dir } };
IEnumerable files = finder.AllAssemblies();
return Search(files);
}
private readonly object AddAssemblyLock = new();
/// Adds an assembly outside the 'search' context.
internal AssemblyData AddAssembly(string path, Assembly loadedAssembly)
{
// This fixes a race condition when TypeData.FromType() is called in parallel.
lock (AddAssemblyLock)
{
var asm = graph.AddAssemblyInfo(path, loadedAssembly);
PluginsInAssemblyRecursive(asm);
return asm;
}
}
private TypeData PluginFromPluginRecursive(TypeData type)
{
if (AllTypes.TryGetValue(type.Name, out var plugin)) return plugin;
plugin = type.Clone();
AllTypes.Add(plugin.Name, plugin);
foreach (var oldBaseType in type.BaseTypes ?? [])
{
var basetype = PluginFromPluginRecursive(oldBaseType);
if (basetype != null)
{
basetype.AddDerivedType(plugin);
plugin.AddBaseType(basetype);
plugin.AddPluginTypes(basetype.PluginTypes);
}
}
foreach (var oldInterfaceType in type.PluginTypes ?? [])
{
var @interface = PluginFromPluginRecursive(oldInterfaceType);
if (@interface != null)
{
plugin.AddPluginType(@interface);
}
}
plugin.FinalizeCreation();
if (plugin.PluginTypes != null)
{
PluginTypes.Add(plugin);
}
return plugin;
}
private void AbsorbLoadedPlugins(PluginSearcher searcher)
{
// The searcher we are currently absorbing can be mutated by calls to e.g. TypeData.FromType()
// We need to guard against access to searcher.AllTypes while we are absorbing it.
lock (searcher.AddAssemblyLock)
{
// Create new instances of all the plugins which were already loaded.
// This solves problems related to scanning new plugin versions after a package upgrade.
// The current process should keep displaying information about the loaded plugin instead of whatever is on disk.
TypeData[] alreadyLoaded = [.. searcher.AllTypes.Values.Where(x => x.IsAssemblyLoaded())];
foreach (var m in alreadyLoaded)
{
PluginFromPluginRecursive(m);
}
}
}
///
/// Searches assembly files and returns all the plugin types found in those.
/// The search will also populate a complete list of types searched in the AllTypes property
/// and all Assemblies found in the Assemblies property.
/// Subsequent calls to this method will add to those properties.
///
public IEnumerable Search(IEnumerable files)
{
Stopwatch timer = Stopwatch.StartNew();
graph ??= new AssemblyDependencyGraph(Option);
Assemblies = graph.Generate(files);
log.Debug(timer, "Ordered {0} assemblies according to references.", Assemblies.Count());
foreach (AssemblyData asm in Assemblies)
{
PluginsInAssemblyRecursive(asm);
}
return PluginTypes;
}
internal readonly TypeData PluginMarkerType = new TypeData(typeof(ITapPlugin).FullName);
private void PluginsInAssemblyRecursive(AssemblyData asm)
{
// This is possible if the package containing the file was uninstalled
if (!File.Exists(asm.Location))
return;
CurrentAsm = asm;
ReadPrivateTypesInCurrentAsm = false;
TypesInCurrentAsm = new Dictionary();
using (FileStream file = new FileStream(asm.Location, FileMode.Open, FileAccess.Read))
using (PEReader header = new PEReader(file, PEStreamOptions.LeaveOpen))
{
CurrentReader = header.GetMetadataReader();
foreach (CustomAttributeHandle attrHandle in CurrentReader.CustomAttributes)
{
CustomAttribute attr = CurrentReader.GetCustomAttribute(attrHandle);
bool isPluginAssemblyAttribute = false;
if (attr.Constructor.Kind == HandleKind.MethodDefinition)
{
MethodDefinition ctor = CurrentReader.GetMethodDefinition((MethodDefinitionHandle)attr.Constructor);
isPluginAssemblyAttribute = MatchFullName(CurrentReader, ctor.GetDeclaringType(), "OpenTap", nameof(PluginAssemblyAttribute));
}
else if (attr.Constructor.Kind == HandleKind.MemberReference)
{
var ctor = CurrentReader.GetMemberReference((MemberReferenceHandle)attr.Constructor);
isPluginAssemblyAttribute = MatchFullName(CurrentReader, ctor.Parent, "OpenTap", nameof(PluginAssemblyAttribute));
}
if(isPluginAssemblyAttribute)
{
var valueString = attr.DecodeValue(new CustomAttributeTypeProvider());
ReadPrivateTypesInCurrentAsm = (bool)valueString.FixedArguments[0].Value;
if (valueString.FixedArguments.Count() > 1)
{
string initMethodName = valueString.FixedArguments.ElementAt(1).Value.ToString();
asm.PluginAssemblyAttribute = new PluginAssemblyAttribute(ReadPrivateTypesInCurrentAsm, initMethodName);
}
else
asm.PluginAssemblyAttribute = new PluginAssemblyAttribute(ReadPrivateTypesInCurrentAsm);
break;
}
}
foreach (var typeDefHandle in CurrentReader.TypeDefinitions)
{
try
{
PluginFromTypeDefRecursive(typeDefHandle);
}
catch
{
// fixes an issue in the plugin searcher if it tries to scan an assembly with native types in it.
}
}
}
}
///
/// All types found by the search indexed by their SearchAssembly.FullName.
/// Null if PluginSearcher.Search has not been called.
///
internal readonly Dictionary AllTypes = [];
///
/// Types found by the search that implement ITapPlugin.
/// Null if PluginSearcher.Search has not been called.
///
internal readonly HashSet PluginTypes = [];
private AssemblyData CurrentAsm;
private bool ReadPrivateTypesInCurrentAsm = false;
private Dictionary TypesInCurrentAsm;
private MetadataReader CurrentReader;
private TypeData PluginFromEntityRecursive(EntityHandle handle)
{
switch (handle.Kind)
{
case HandleKind.TypeReference:
return PluginFromTypeRef((TypeReferenceHandle)handle);
case HandleKind.TypeDefinition:
return PluginFromTypeDefRecursive((TypeDefinitionHandle)handle);
case HandleKind.TypeSpecification:
var baseSpec = CurrentReader.GetTypeSpecification((TypeSpecificationHandle)handle);
try
{
return baseSpec.DecodeSignature(new SignatureTypeProvider(this), AllTypes);
}
catch
{
return null;
}
default:
return null;
}
}
private TypeData PluginFromTypeRef(TypeReferenceHandle handle)
{
string ifaceFullName = GetFullName(CurrentReader,handle);
if (AllTypes.TryGetValue(ifaceFullName, out var tp))
return tp;
return null; // This is not a type that we care about (not defined in any of the files the searcher is given)
}
private static string valueTypeName = typeof(ValueType).FullName;
private static readonly ConcurrentDictionary ValueTypeMap = new ConcurrentDictionary();
private bool IsValueType(TypeDefinition typeDef, string typeName = null)
{
bool helper(string s)
{
try
{
if (s == valueTypeName) return true;
var baseType = typeDef.BaseType;
switch (baseType.Kind)
{
case HandleKind.TypeReference:
var tr = (TypeReferenceHandle)baseType;
var r = CurrentReader.GetTypeReference(tr);
return CurrentReader.GetString(r.Name) == "ValueType";
case HandleKind.TypeDefinition:
var td = (TypeDefinitionHandle)baseType;
if (td.IsNil) return false;
var d = CurrentReader.GetTypeDefinition(td);
return IsValueType(d);
default:
return false;
}
}
catch
{
// This should be rare, and if the reader can't resolve some base type we can't do anything about it.
// Just assume it isn't a valuetype and move on.
return false;
}
}
typeName ??= GetTypeName(typeDef);
return ValueTypeMap.GetOrAdd(typeName, helper);
}
private string GetTypeName(TypeDefinition td)
{
string typeName;
TypeDefinitionHandle declaringTypeHandle = td.GetDeclaringType();
if (declaringTypeHandle.IsNil)
{
typeName = string.Format("{0}.{1}", CurrentReader.GetString(td.Namespace),
CurrentReader.GetString(td.Name));
}
else
{
// This is a nested type
TypeData declaringType = PluginFromTypeDefRecursive(declaringTypeHandle);
if (declaringType == null)
return null;
typeName = string.Format("{0}+{1}", declaringType.Name, CurrentReader.GetString(td.Name));
}
return typeName;
}
private static HashSet warnOnceLookup = new HashSet();
private TypeData PluginFromTypeDefRecursive(TypeDefinitionHandle handle)
{
if (TypesInCurrentAsm.TryGetValue(handle, out var result))
return result;
TypeDefinition typeDef = CurrentReader.GetTypeDefinition(handle);
var typeAttributes = typeDef.Attributes;
if (ReadPrivateTypesInCurrentAsm)
{
if ((typeAttributes & TypeAttributes.VisibilityMask) != TypeAttributes.NotPublic &&
(typeAttributes & TypeAttributes.VisibilityMask) != TypeAttributes.Public &&
(typeAttributes & TypeAttributes.VisibilityMask) != TypeAttributes.NestedPrivate &&
(typeAttributes & TypeAttributes.VisibilityMask) != TypeAttributes.NestedPublic)
return null;
}
else
{
if ((typeAttributes & TypeAttributes.VisibilityMask) != TypeAttributes.Public &&
(typeAttributes & TypeAttributes.VisibilityMask) != TypeAttributes.NestedPublic)
return null;
}
var typeName = GetTypeName(typeDef);
if (typeName == null) return null;
if (AllTypes.TryGetValue(typeName, out var existingPlugin))
{
if (existingPlugin.Assembly.Name == CurrentAsm.Name)
{
// we assume this is the same plugin, just in another copy of the dll
// This can happen if you are creating a package with file in a subfoler.
// That file will get copied, and we end up with it twice in the installation dir
// in that case it is important for the logic in EnumeratePlugins that this assembly also has the plugin types listed.
if (existingPlugin.PluginTypes != null &&
(CurrentAsm.PluginTypes == null || !CurrentAsm.PluginTypes.Any(t => t.Name == existingPlugin.Name)))
{
CurrentAsm.AddPluginType(existingPlugin);
}
}
else
{
// DescendsTo will try to load the plugin. Manually walk the basetypes instead.
var basetypes = new Queue();
basetypes.Enqueue(existingPlugin);
bool isPlugin = false;
while (basetypes.Any())
{
var t = basetypes.Dequeue();
if (PluginMarkerType.Equals(t))
{
isPlugin = true;
break;
}
if (t.BaseTypes != null)
{
foreach (var bt in t.BaseTypes)
{
basetypes.Enqueue(bt);
}
}
}
if (isPlugin && existingPlugin.IsBrowsable)
{
var key = $"{typeName}-{existingPlugin.Assembly.Name}-{CurrentAsm.Name}";
if (warnOnceLookup.Add(key))
{
log.Warning($"Plugin with duplicate type name {typeName} defined in '{existingPlugin.Assembly.Name}' and '{CurrentAsm.Name}'");
}
}
}
return existingPlugin;
}
TypeData plugin = new TypeData(typeName);
if (plugin.Name == PluginMarkerType.Name)
{
PluginMarkerType.Assembly = CurrentAsm;
PluginMarkerType.TypeAttributes = typeDef.Attributes;
AllTypes.Add(PluginMarkerType.Name, PluginMarkerType);
TypesInCurrentAsm.Add(handle, PluginMarkerType);
CurrentAsm.AddPluginType(PluginMarkerType);
return PluginMarkerType;
}
plugin.TypeAttributes = typeDef.Attributes;
plugin.Assembly = CurrentAsm;
List supportedPlatforms = null;
foreach (CustomAttributeHandle attrHandle in typeDef.GetCustomAttributes())
{
CustomAttribute attr = CurrentReader.GetCustomAttribute(attrHandle);
string attributeFullName = "";
if (attr.Constructor.Kind == HandleKind.MethodDefinition)
{
MethodDefinition ctor =
CurrentReader.GetMethodDefinition((MethodDefinitionHandle) attr.Constructor);
attributeFullName = GetFullName(CurrentReader, ctor.GetDeclaringType());
}
else if (attr.Constructor.Kind == HandleKind.MemberReference)
{
var ctor = CurrentReader.GetMemberReference((MemberReferenceHandle) attr.Constructor);
attributeFullName = GetFullName(CurrentReader, ctor.Parent);
}
switch (attributeFullName)
{
case "System.Runtime.Versioning.SupportedOSPlatformAttribute":
{
var valueString = attr.DecodeValue(new CustomAttributeTypeProvider(AllTypes));
if (valueString.FixedArguments.Length == 1 && valueString.FixedArguments[0].Value is string platform)
{
supportedPlatforms ??= [];
supportedPlatforms.Add(platform);
}
}
break;
case "OpenTap.DisplayAttribute":
{
var valueString = attr.DecodeValue(new CustomAttributeTypeProvider(AllTypes));
string displayName =
GetStringIfNotNull(valueString.FixedArguments[0]
.Value); // the first argument to the DisplayAttribute constructor is the display name
string displayDescription = GetStringIfNotNull(valueString.FixedArguments[1].Value);
string displayGroup = GetStringIfNotNull(valueString.FixedArguments[2].Value);
double displayOrder = (double)valueString.FixedArguments[3].Value;
bool displayCollapsed = bool.Parse(GetStringIfNotNull(valueString.FixedArguments[4].Value));
string[] displayGroups = GetStringArrayIfNotNull(valueString.FixedArguments[5].Value);
DisplayAttribute attrInstance = new DisplayAttribute(displayName, displayDescription,
displayGroup, displayOrder, displayCollapsed, displayGroups);
plugin.Display = attrInstance;
}
break;
case "OpenTap.HelpLinkAttribute":
{
var valueString = attr.DecodeValue(new CustomAttributeTypeProvider(AllTypes));
if (valueString.FixedArguments.Length == 1 && valueString.FixedArguments[0].Value is string helpLink)
{
plugin.HelpLink = new HelpLinkAttribute(helpLink);
}
else
{
plugin.HelpLink = new HelpLinkAttribute();
}
}
break;
case "System.ComponentModel.BrowsableAttribute":
{
var valueString = attr.DecodeValue(new CustomAttributeTypeProvider());
plugin.IsBrowsable = bool.Parse(valueString.FixedArguments.First().Value.ToString());
}
break;
default:
break;
}
}
// If the plugin type is not compatible with this platform, don't add it.
if (supportedPlatforms != null)
{
bool supported = supportedPlatforms.Any(platform =>
{
var cmp = StringComparison.OrdinalIgnoreCase;
if (platform.StartsWith("windows", cmp)) return OperatingSystem.Current == OperatingSystem.Windows;
if (platform.StartsWith("linux", cmp)) return OperatingSystem.Current == OperatingSystem.Linux;
if (platform.StartsWith("macos", cmp)) return OperatingSystem.Current == OperatingSystem.MacOS;
if (platform.StartsWith("osx", cmp)) return OperatingSystem.Current == OperatingSystem.MacOS;
return false;
});
if (!supported) return null;
}
TypesInCurrentAsm.Add(handle, plugin);
AllTypes.Add(plugin.Name, plugin);
if (!typeDef.BaseType.IsNil)
{
TypeData baseType = PluginFromEntityRecursive(typeDef.BaseType);
if (baseType != null)
{
baseType.AddDerivedType(plugin);
plugin.AddBaseType(baseType);
plugin.AddPluginTypes(baseType.PluginTypes);
}
}
foreach (InterfaceImplementationHandle ifaceHandle in typeDef.GetInterfaceImplementations())
{
EntityHandle ifaceEntity = CurrentReader.GetInterfaceImplementation(ifaceHandle).Interface;
TypeData iface = PluginFromEntityRecursive(ifaceEntity);
if (iface == null)
continue;
iface.AddDerivedType(plugin);
plugin.AddBaseType(iface);
plugin.AddPluginTypes(iface.PluginTypes);
if (iface.Name == PluginMarkerType.Name && plugin.PluginTypes == null)
{
plugin.AddPluginType(plugin); // this inherits directly from ITapPlugin (otherwise it should have been picked up earlier)
}
}
plugin.FinalizeCreation();
// Check if the type is constructable by inspecting the available constructors
if (plugin.createInstanceSet == false)
{
// Abstract types and interfaces cannot be instantiated
if (typeAttributes.HasFlag(TypeAttributes.Interface) || typeAttributes.HasFlag(TypeAttributes.Abstract))
{
plugin.CanCreateInstance = false;
}
// It is not possible to instantiate types if they have unresolved generic parameters.
// Since we are currently reflecing an unloaded assembly, it is impossible for generic parameters
// to be resolved. If there are generic parameters, this typedata must therefore be unconstroctable.
// Once the type is actually loaded, whether an instance can be created for resolved instances
// of this type will be computed differently in the TypeData implementation.
else if (typeDef.GetGenericParameters().Count != 0)
{
plugin.CanCreateInstance = false;
}
else if (IsValueType(typeDef, typeName))
{
plugin.CanCreateInstance = true;
}
else
{
// The type can only be instantiated if it has a parameter-less constructor which does not require type arguments
bool hasGenericParameters(MethodDefinition m)
{
return m.GetGenericParameters().Count > 0;
}
bool hasParameters(MethodDefinition m)
{
return m.GetParameters().Count > 0;
}
foreach (var methodHandle in typeDef.GetMethods())
{
var m = CurrentReader.GetMethodDefinition(methodHandle);
// This method is applicable if it is public, non-static, and has the RTSpecialName attribute
// The RTSpecialName attribute means that the method has a special significance explained by its name.
// All constructors will have this attribute, but most user-defined methods will not.
var attributes = m.Attributes;
var applicable = attributes.HasFlag(MethodAttributes.Public) &&
attributes.HasFlag(MethodAttributes.Static) == false &&
attributes.HasFlag(MethodAttributes.RTSpecialName);
if (!applicable)
continue;
if (CurrentReader.GetString(m.Name) != ".ctor")
continue;
if (hasGenericParameters(m) || hasParameters(m))
{
plugin.CanCreateInstance = false;
continue;
}
// We know that the type is constructable, so we can stop searching.
plugin.CanCreateInstance = true;
break;
}
}
}
if (plugin.PluginTypes != null)
{
PluginTypes.Add(plugin);
CurrentAsm.AddPluginType(plugin);
if(plugin.Assembly.RawVersion == null)
{
foreach (CustomAttributeHandle attrHandle in CurrentReader.GetAssemblyDefinition().GetCustomAttributes())
{
CustomAttribute attr = CurrentReader.GetCustomAttribute(attrHandle);
if (attr.Constructor.Kind == HandleKind.MemberReference)
{
var ctor = CurrentReader.GetMemberReference((MemberReferenceHandle)attr.Constructor);
string attributeFullName = GetFullName(CurrentReader, ctor.Parent);
if(attributeFullName == typeof(AssemblyInformationalVersionAttribute).FullName)
{
var valueString = attr.DecodeValue(new CustomAttributeTypeProvider(AllTypes));
plugin.Assembly.RawVersion = GetStringIfNotNull(valueString.FixedArguments[0].Value);
}
}
}
}
}
return plugin;
}
private static string GetStringIfNotNull(object obj)
{
return obj?.ToString();
}
private static string[] GetStringArrayIfNotNull(object obj)
{
if (obj == null)
return null;
return (obj as IEnumerable>).Select(o => o.Value.ToString()).ToArray();
}
///
/// Helper to get the full name (namespace + name) of the type referenced by a TypeDefinitionHandle or TypeReferenceHandle
///
static string GetFullName(MetadataReader metadata, EntityHandle handle)
{
switch (handle.Kind)
{
case HandleKind.TypeDefinition:
var def = metadata.GetTypeDefinition((TypeDefinitionHandle)handle);
return String.Format("{0}.{1}", metadata.GetString(def.Namespace), metadata.GetString(def.Name));
case HandleKind.TypeReference:
var r = metadata.GetTypeReference((TypeReferenceHandle)handle);
return String.Format("{0}.{1}", metadata.GetString(r.Namespace), metadata.GetString(r.Name));
//case HandleKind.TypeSpecification:
// var s = metadata.GetTypeSpecification((TypeSpecificationHandle)handle);
// s.DecodeSignature(new CustomAttributeTypeProvider(), null);
// return new PluginType ();
default:
return null;
}
}
bool MatchFullName(MetadataReader metadata, EntityHandle handle, string matchNamespace, string matchName)
{
switch (handle.Kind)
{
case HandleKind.TypeDefinition:
var def = metadata.GetTypeDefinition((TypeDefinitionHandle)handle);
return metadata.GetString(def.Namespace) == matchNamespace && metadata.GetString(def.Name) == matchName;
case HandleKind.TypeReference:
var r = metadata.GetTypeReference((TypeReferenceHandle)handle);
return metadata.GetString(r.Namespace) == matchNamespace && metadata.GetString(r.Name) == matchName;
default:
return false;
}
}
#region Providers needed by the Metadata API (not really important the way we use the API)
struct CustomAttributeTypeProvider : ICustomAttributeTypeProvider
{
private Dictionary _types;
public CustomAttributeTypeProvider(Dictionary types)
{
_types = types;
}
public TypeData GetPrimitiveType(PrimitiveTypeCode typeCode)
{
return null;
}
public TypeData GetSystemType()
{
return _types["System.Type"];
}
public TypeData GetSZArrayType(TypeData elementType)
{
return null;
}
public TypeData GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind)
{
string fullName = GetFullName(reader,handle);
return _types[fullName];
}
public TypeData GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind)
{
string fullName = GetFullName(reader, handle);
return _types[fullName];
}
public TypeData GetTypeFromSerializedName(string name)
{
if (name == null)
return null;
return _types[name];
}
public PrimitiveTypeCode GetUnderlyingEnumType(TypeData type)
{
throw new NotImplementedException();
}
public bool IsSystemType(TypeData type)
{
return type.Name == "System.Type";
}
}
class SignatureTypeProvider : ISignatureTypeProvider>
{
private readonly PluginSearcher _Searcher;
public SignatureTypeProvider(PluginSearcher searcher)
{
_Searcher = searcher;
}
public TypeData GetArrayType(TypeData elementType, ArrayShape shape)
{
throw new NotImplementedException();
}
public TypeData GetByReferenceType(TypeData elementType)
{
throw new NotImplementedException();
}
public TypeData GetFunctionPointerType(MethodSignature signature)
{
throw new NotImplementedException();
}
public TypeData GetGenericInstantiation(TypeData genericType, ImmutableArray typeArguments)
{
return genericType;
}
public TypeData GetGenericMethodParameter(Dictionary genericContext, int index)
{
throw new NotImplementedException();
}
public TypeData GetGenericTypeParameter(Dictionary genericContext, int index)
{
return null;
}
public TypeData GetModifiedType(TypeData modifier, TypeData unmodifiedType, bool isRequired)
{
throw new NotImplementedException();
}
public TypeData GetPinnedType(TypeData elementType)
{
throw new NotImplementedException();
}
public TypeData GetPointerType(TypeData elementType)
{
throw new NotImplementedException();
}
public TypeData GetPrimitiveType(PrimitiveTypeCode typeCode)
{
return new TypeData("System." + typeCode);
}
public TypeData GetSZArrayType(TypeData elementType)
{
return elementType != null ? new TypeData(elementType.Name) : null;
}
public TypeData GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind)
{
return _Searcher.PluginFromTypeDefRecursive(handle);
}
public TypeData GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind)
{
return _Searcher.PluginFromTypeRef(handle);
}
public TypeData GetTypeFromSpecification(MetadataReader reader, Dictionary genericContext, TypeSpecificationHandle handle, byte rawTypeKind)
{
throw new NotImplementedException();
}
}
#endregion
internal AssemblyData GetAssemblyData(Assembly asm) => graph.FindAssemblyData(asm);
}
}