1 //
2 // Copyright (c) 2010-2024 Antmicro
3 // Copyright (c) 2011-2015 Realtime Embedded
4 //
5 // This file is licensed under the MIT License.
6 // Full license text is available in 'licenses/MIT.txt'.
7 //
8 using System;
9 using System.Linq;
10 using System.IO;
11 using System.Diagnostics;
12 using Antmicro.Renode.Logging;
13 using Antmicro.Renode.Peripherals;
14 using Mono.Cecil;
15 using System.Reflection;
16 using System.Collections.Generic;
17 using Antmicro.Renode.Plugins;
18 using Antmicro.Renode.Exceptions;
19 
20 namespace Antmicro.Renode.Utilities
21 {
22     public class TypeManager : IDisposable
23     {
TypeManager()24         static TypeManager()
25         {
26             string assemblyLocation;
27             var isBundled = AssemblyHelper.TryInitializeBundledAssemblies();
28 
29             Instance = new TypeManager(isBundled);
30             if(isBundled)
31             {
32                 foreach(var name in AssemblyHelper.GetBundledAssembliesNames())
33                 {
34                     Instance.ScanFile(name, bundled: true);
35                 }
36 
37                 // in case of a bundled version `Assembly.GetExecutingAssembly().Location` returns an empty string
38                 assemblyLocation = Directory.GetCurrentDirectory();
39             }
40             else
41             {
42                 assemblyLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
43             }
44             Instance.Scan(assemblyLocation);
45         }
46 
47         public static TypeManager Instance { get; private set; }
48 
49         private Action<Type> autoLoadedTypeEvent;
50         private readonly List<Type> autoLoadedTypes = new List<Type>();
51         //AutoLoadedType will fire for each type even if the event is attached after the loading.
52         private object autoLoadedTypeLocker = new object();
53         public event Action<Type> AutoLoadedType
54         {
55             add
56             {
57                 // this lock is needed because it happens that two
58                 // threads add the event simulataneously and an exception is rised
59                 lock(autoLoadedTypeLocker)
60                 {
61                     if(value != null)
62                     {
63                         foreach(var type in autoLoadedTypes)
64                         {
65                             value(type);
66                         }
67                         autoLoadedTypeEvent += value;
68                     }
69                 }
70             }
71             remove
72             {
73                 lock(autoLoadedTypeLocker)
74                 {
75                     autoLoadedTypeEvent -= value;
76                 }
77             }
78         }
79 
Scan()80         public void Scan()
81         {
82             Scan(Directory.GetCurrentDirectory());
83         }
84 
ScanFile(string path, bool bundled = false)85         public bool ScanFile(string path, bool bundled = false)
86         {
87             lock(dictSync)
88             {
89                 Logger.LogAs(this, LogLevel.Noisy, "Loading assembly {0}.", path);
90                 ClearExtensionMethodsCache();
91                 BuildAssemblyCache();
92                 if(!AnalyzeAssembly(path, bundled: bundled))
93                 {
94                     return false;
95                 }
96                 assemblyFromAssemblyPath = null;
97                 Logger.LogAs(this, LogLevel.Noisy, "Assembly loaded, there are now {0} types in dictionaries.", GetTypeCount());
98 
99                 return true;
100             }
101         }
102 
Scan(string path, bool recursive = false)103         public void Scan(string path, bool recursive = false)
104         {
105             lock(dictSync)
106             {
107                 Logger.LogAs(this, LogLevel.Noisy, "Scanning directory {0}.", path);
108                 var stopwatch = Stopwatch.StartNew();
109                 ClearExtensionMethodsCache();
110                 BuildAssemblyCache();
111                 ScanInner(path, recursive);
112                 assemblyFromAssemblyPath = null;
113                 stopwatch.Stop();
114                 Logger.LogAs(this, LogLevel.Noisy, "Scanning took {0}s, there are now {1} types in dictionaries.", Misc.NormalizeDecimal(stopwatch.Elapsed.TotalSeconds),
115                           GetTypeCount());
116             }
117         }
118 
GetExtensionMethods(Type type)119         public IEnumerable<MethodInfo> GetExtensionMethods(Type type)
120         {
121             lock(dictSync)
122             {
123                 if(extensionMethodsFromThisType.ContainsKey(type))
124                 {
125                     return extensionMethodsFromThisType[type];
126                 }
127                 var fullName = type.FullName;
128                 Logger.LogAs(this, LogLevel.Noisy, "Binding extension methods for {0}.", fullName);
129                 var methodInfos = GetExtensionMethodsInner(type).ToArray();
130                 Logger.LogAs(this, LogLevel.Noisy, "{0} methods bound.", methodInfos.Length);
131                 // we can put it into cache now
132                 extensionMethodsFromThisType.Add(type, methodInfos);
133                 return methodInfos;
134             }
135         }
136 
GetTypeByName(string name, Func<ICollection<string>, string> assemblyResolver = null)137         public Type GetTypeByName(string name, Func<ICollection<string>, string> assemblyResolver = null)
138         {
139             var result = TryGetTypeByName(name, assemblyResolver);
140             if(result == null)
141             {
142                 throw new KeyNotFoundException(string.Format("Given type {0} was not found in any of the known assemblies.", name));
143             }
144             return result;
145         }
146 
TryGetTypeByName(string name, Func<ICollection<string>, string> assemblyResolver = null)147         public Type TryGetTypeByName(string name, Func<ICollection<string>, string> assemblyResolver = null)
148         {
149             lock(dictSync)
150             {
151                 AssemblyDescription assembly;
152                 if(assemblyFromTypeName.TryGetValue(name, out assembly))
153                 {
154                     return GetTypeWithLazyLoad(name, assembly.FullName, assembly.Path);
155                 }
156                 if(assembliesFromTypeName.ContainsKey(name))
157                 {
158                     var possibleAssemblies = assembliesFromTypeName[name];
159                     if(assemblyResolver == null)
160                     {
161                         throw new InvalidOperationException(string.Format(
162                             "Type {0} could possibly be loaded from assemblies {1}, but no assembly resolver was provided.",
163                             name, possibleAssemblies.Select(x => x.Path).Aggregate((x, y) => x + ", " + y)));
164                     }
165                     var selectedAssembly = assemblyResolver(possibleAssemblies.Select(x => x.Path).ToList());
166                     var selectedAssemblyDescription = possibleAssemblies.FirstOrDefault(x => x.Path == selectedAssembly);
167                     if(selectedAssemblyDescription == null)
168                     {
169                         throw new InvalidOperationException(string.Format(
170                             "Assembly resolver returned path {0} which is not one of the proposed paths {1}.",
171                             selectedAssembly, possibleAssemblies.Select(x => x.Path).Aggregate((x, y) => x + ", " + y)));
172                     }
173                     // once conflict is resolved, we can move this type to assemblyFromTypeName
174                     assembliesFromTypeName.Remove(name);
175                     assemblyFromTypeName.Add(name, selectedAssemblyDescription);
176                     return GetTypeWithLazyLoad(name, selectedAssemblyDescription.FullName, selectedAssembly);
177                 }
178                 return null;
179             }
180         }
181 
GetAvailablePeripherals(Type attachableTo = null)182         public IEnumerable<TypeDescriptor> GetAvailablePeripherals(Type attachableTo = null)
183         {
184             if(attachableTo == null)
185             {
186                 return foundPeripherals.Where(td => td.IsClass && !td.IsAbstract && td.Methods.Any(m => m.IsConstructor && m.IsPublic)).Select(x => new TypeDescriptor(x));
187             }
188 
189             var ifaces = attachableTo.GetInterfaces()
190                 .Where(i =>
191                     i.IsGenericType &&
192                     i.GetGenericTypeDefinition() == typeof(Antmicro.Renode.Core.Structure.IPeripheralRegister<,>))
193                 .Select(i => i.GetGenericArguments()[0]).Distinct();
194 
195             return foundPeripherals
196                .Where(td =>
197                     td.IsClass &&
198                     !td.IsAbstract &&
199                     td.Methods.Any(m => m.IsConstructor && m.IsPublic) &&
200                     ifaces.Any(iface => ImplementsInterface(td, iface)))
201                 .Select(x => new TypeDescriptor(x));
202         }
203 
Dispose()204         public void Dispose()
205         {
206             PluginManager.Dispose();
207         }
208 
209         public Type[] AutoLoadedTypes { get { return autoLoadedTypes.ToArray(); } }
210         public IEnumerable<PluginDescriptor> AvailablePlugins { get { return foundPlugins.ToArray(); } }
211         public PluginManager PluginManager { get; set; }
212 
ImplementsInterface(TypeDefinition type, Type @interface)213         private bool ImplementsInterface(TypeDefinition type, Type @interface)
214         {
215             if(type.GetFullNameOfMember() == @interface.FullName)
216             {
217                 return true;
218             }
219 
220         #if NET
221             return (type.BaseType != null && ImplementsInterface(ResolveInner(type.BaseType), @interface)) || type.Interfaces.Any(i => ImplementsInterface(ResolveInner(i.InterfaceType), @interface));
222         #else
223             return (type.BaseType != null && ImplementsInterface(ResolveInner(type.BaseType), @interface)) || type.Interfaces.Any(i => ImplementsInterface(ResolveInner(i), @interface));
224         #endif
225         }
226 
TypeManager(bool isBundled)227         private TypeManager(bool isBundled)
228         {
229             assembliesFromTypeName = new Dictionary<string, List<AssemblyDescription>>();
230             assemblyFromTypeName = new Dictionary<string, AssemblyDescription>();
231             assemblyFromAssemblyName = new Dictionary<string, AssemblyDescription>();
232             extensionMethodsFromThisType = new Dictionary<Type, MethodInfo[]>();
233             extensionMethodsTraceFromTypeFullName = new Dictionary<string, HashSet<MethodDescription>>();
234             knownDirectories = new HashSet<string>();
235             dictSync = new object();
236             AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;
237 
238             foundPeripherals = new List<TypeDefinition>();
239             foundPlugins = new List<PluginDescriptor>();
240             PluginManager = new PluginManager();
241 
242             this.isBundled = isBundled;
243         }
244 
ResolveAssembly(object sender, ResolveEventArgs args)245         private Assembly ResolveAssembly(object sender, ResolveEventArgs args)
246         {
247             lock(dictSync)
248             {
249                 AssemblyDescription description;
250                 var simpleName = ExtractSimpleName(args.Name);
251                 if(assemblyFromAssemblyName.TryGetValue(simpleName, out description))
252                 {
253                     if(args.Name == description.FullName)
254                     {
255                         Logger.LogAs(this, LogLevel.Noisy, "Assembly '{0}' resolved by exact match from '{1}'.", args.Name, description.Path);
256                     }
257                     else
258                     {
259                         Logger.LogAs(this, LogLevel.Noisy, "Assembly '{0}' resolved by simple name '{1}' from '{2}'.", args.Name, simpleName, description.Path);
260                     }
261                     return Assembly.LoadFrom(description.Path);
262 
263                 }
264                 return null;
265             }
266         }
267 
ScanInner(string path, bool recursive)268         private void ScanInner(string path, bool recursive)
269         {
270             // TODO: case insensitive
271             foreach(var assembly in Directory.GetFiles(path, "*.dll").Union(Directory.GetFiles(path, "*.exe")))
272             {
273                 if(assemblyBlacklist.Any(x => assembly.Contains(x)))
274                 {
275                     Logger.LogAs(this, LogLevel.Noisy, "Ignoring assembly '{0}'", assembly);
276                     continue;
277                 }
278                 AnalyzeAssembly(assembly, throwOnBadImage: false);
279             }
280             if(recursive)
281             {
282                 foreach(var subdir in Directory.GetDirectories(path))
283                 {
284                     ScanInner(subdir, recursive);
285                 }
286             }
287         }
288 
ExtractSimpleName(string name)289         private static string ExtractSimpleName(string name)
290         {
291             return name.Split(',')[0];
292         }
293 
GetUnifiedTypeName(Type type)294         private string GetUnifiedTypeName(Type type)
295         {
296             // type names returned by Type.ToString() and TypeReference.FullName differ by the type of brackets
297             return type.ToString().Replace('[', '<').Replace(']', '>');
298         }
299 
GetExtensionMethodsInner(Type type)300         private IEnumerable<MethodInfo> GetExtensionMethodsInner(Type type)
301         {
302             var fullName = GetUnifiedTypeName(type);
303             IEnumerable<MethodInfo> methodInfos;
304             if(!extensionMethodsTraceFromTypeFullName.ContainsKey(fullName))
305             {
306                 methodInfos = new MethodInfo[0];
307             }
308             else
309             {
310                 var methodDescriptions = extensionMethodsTraceFromTypeFullName[fullName];
311                 var result = new MethodInfo[methodDescriptions.Count];
312                 var i = -1;
313                 foreach(var methodDescription in methodDescriptions)
314                 {
315                     i++;
316                     var describedType = GetTypeByName(methodDescription.TypeFullName);
317                     if(!methodDescription.IsOverloaded)
318                     {
319                         // method's name is unique
320                         result[i] = describedType.GetMethod(methodDescription.Name);
321                     }
322                     else
323                     {
324                         var methodsInClass = describedType.GetMethods();
325                         var matchedMethod = methodsInClass.Single(x => x.Name == methodDescription.Name && GetMethodSignature(x) == methodDescription.Signature);
326                         result[i] = matchedMethod;
327                     }
328                 }
329                 methodInfos = result;
330             }
331             // we also obtain EM for base type and interfaces
332             if(type.BaseType != null)
333             {
334                 methodInfos = methodInfos.Union(GetExtensionMethodsInner(type.BaseType));
335             }
336             foreach(var iface in type.GetInterfaces())
337             {
338                 methodInfos = methodInfos.Union(GetExtensionMethodsInner(iface));
339             }
340             methodInfos = methodInfos.ToArray();
341             return methodInfos;
342         }
343 
GetTypeWithLazyLoad(string name, string assemblyFullName, string path)344         private Type GetTypeWithLazyLoad(string name, string assemblyFullName, string path)
345         {
346             var fullName = string.Format("{0}, {1}", name, assemblyFullName);
347             var type = Type.GetType(fullName);
348             if(type == null)
349             {
350                 //While Type.GetType on Mono is very liberal, finding the types even without the AQN provided, on Windows we have to either provide the assembly to search in or the full type name with AQN.
351                 //This is useful when we're generating dynamic assemblies, via loading a cs file and compiling it ad-hoc.
352                 var assembly = Assembly.LoadFrom(path);
353                 type = assembly.GetType(name, true);
354                 Logger.LogAs(this, LogLevel.Noisy, "Loaded assembly {0} ({1} triggered).", path, type.FullName);
355             }
356             return type;
357         }
358 
BuildAssemblyCache()359         private void BuildAssemblyCache()
360         {
361             assemblyFromAssemblyPath = new Dictionary<string, AssemblyDescription>();
362             foreach(var assembly in assemblyFromTypeName.Select(x => x.Value).Union(assembliesFromTypeName.SelectMany(x => x.Value)).Distinct())
363             {
364                 assemblyFromAssemblyPath.Add(assembly.Path, assembly);
365             }
366             Logger.LogAs(this, LogLevel.Noisy, "Assembly cache with {0} distinct assemblies built.", assemblyFromAssemblyPath.Count);
367         }
368 
ResolveInner(TypeReference tp)369         private TypeDefinition ResolveInner(TypeReference tp)
370         {
371             if(isBundled)
372             {
373                 try
374                 {
375                     var scope = tp.GetElementType().Scope.ToString();
376                     var bundled = AssemblyHelper.GetBundledAssemblyByFullName(scope);
377                     if(bundled != null)
378                     {
379                         if(tp.IsArray)
380                         {
381                             // this supports only one-dimensional arrays for now
382                             var elementType = bundled.MainModule.GetType(tp.Namespace, tp.GetElementType().Name);
383                             return new ArrayType(elementType).Resolve();
384                         }
385 
386                         return bundled.MainModule.GetType(tp.Namespace, tp.Name);
387                     }
388                 }
389                 catch
390                 {
391                     // intentionally do nothing, we'll try to resolve it later
392                 }
393             }
394 
395             try
396             {
397                 return tp.Resolve();
398             }
399             catch
400             {
401                 // we couldn't resolve it in any way, just give up
402                 return null;
403             }
404         }
405 
TryExtractExtensionMethods(TypeDefinition type, out Dictionary<string, HashSet<MethodDescription>> extractedMethods)406         private bool TryExtractExtensionMethods(TypeDefinition type, out Dictionary<string, HashSet<MethodDescription>> extractedMethods)
407         {
408             // type is enclosing type
409             if(!type.IsClass)
410             {
411                 extractedMethods = null;
412                 return false;
413             }
414             var result = false;
415             extractedMethods = new Dictionary<string, HashSet<MethodDescription>>();
416             foreach(var method in type.Methods)
417             {
418                 if(method.IsStatic && method.IsPublic && method.CustomAttributes.Any(x => x.AttributeType.GetFullNameOfMember() == typeof(System.Runtime.CompilerServices.ExtensionAttribute).FullName))
419                 {
420                     // so this is extension method
421                     // let's check the type of the first parameter
422                     var paramType = method.Parameters[0].ParameterType;
423 
424                     if(IsInterestingType(paramType) ||
425                         (paramType.GetFullNameOfMember() == typeof(object).FullName
426                         && method.CustomAttributes.Any(x => x.AttributeType.GetFullNameOfMember() == typeof(ExtensionOnObjectAttribute).FullName)))
427                     {
428                         result = true;
429                         // that's the interesting extension method
430                         var methodDescription = new MethodDescription(type.GetFullNameOfMember(), method.Name, GetMethodSignature(method), true);
431                         if(extractedMethods.ContainsKey(paramType.GetFullNameOfMember()))
432                         {
433                             extractedMethods[paramType.GetFullNameOfMember()].Add(methodDescription);
434                         }
435                         else
436                         {
437                             extractedMethods.Add(paramType.GetFullNameOfMember(), new HashSet<MethodDescription> { methodDescription });
438                         }
439                     }
440                 }
441             }
442             return result;
443         }
444 
IsReferenced(Assembly referencingAssembly, string checkedAssemblyName)445         private static bool IsReferenced(Assembly referencingAssembly, string checkedAssemblyName)
446         {
447             var alreadyVisited = new HashSet<Assembly>();
448             var queue = new Queue<Assembly>();
449             queue.Enqueue(referencingAssembly);
450             while(queue.Count > 0)
451             {
452                 var current = queue.Dequeue();
453                 if(current.FullName == checkedAssemblyName)
454                 {
455                     return true;
456                 }
457                 if(alreadyVisited.Contains(current))
458                 {
459                     continue;
460                 }
461                 alreadyVisited.Add(current);
462                 foreach(var reference in current.GetReferencedAssemblies())
463                 {
464                     try
465                     {
466                         queue.Enqueue(Assembly.Load(reference));
467                     }
468                     catch(FileNotFoundException)
469                     {
470                         // if we could not load references assembly, do nothing
471                     }
472                 }
473             }
474             return false;
475         }
476 
AnalyzeAssembly(string path, bool bundled = false, bool throwOnBadImage = true)477         private bool AnalyzeAssembly(string path, bool bundled = false, bool throwOnBadImage = true)
478         {
479             Logger.LogAs(this, LogLevel.Noisy, "Analyzing assembly {0}.", path);
480             if(assemblyFromAssemblyName.Values.Any(x => x.Path == path))
481             {
482                 Logger.LogAs(this, LogLevel.Warning, "Assembly {0} was already analyzed.", path);
483                 return true;
484             }
485             AssemblyDefinition assembly;
486             try
487             {
488                 assembly = (bundled)
489                     ? AssemblyHelper.GetBundledAssemblyByName(path)
490                     : AssemblyDefinition.ReadAssembly(path);
491             }
492             catch(DirectoryNotFoundException)
493             {
494                 Logger.LogAs(this, LogLevel.Warning, "Could not find file {0} to analyze.", path);
495                 return false;
496             }
497             catch(FileNotFoundException)
498             {
499                 Logger.LogAs(this, LogLevel.Warning, "Could not find file {0} to analyze.", path);
500                 return false;
501             }
502             catch(BadImageFormatException)
503             {
504                 var message = string.Format("File {0} could not be analyzed due to invalid format.", path);
505                 if(throwOnBadImage)
506                 {
507                     throw new RecoverableException(message);
508                 }
509                 // we hush this log because it is issued in binary Windows packages - we look for DLL files, but we
510                 // also bundle libgcc etc.
511                 Logger.LogAs(this, LogLevel.Noisy, message);
512                 return false;
513             }
514             // simple assembly name is required for the mechanism in `ResolveAssembly()`
515             var assemblyName = assembly.Name.Name;
516             if(!assemblyFromAssemblyName.ContainsKey(assemblyName))
517             {
518                 assemblyFromAssemblyName.Add(assemblyName, GetAssemblyDescription(assemblyName, path));
519             }
520             else
521             {
522                 if(path == assemblyFromAssemblyName[assemblyName].Path)
523                 {
524                     return true;
525                 }
526                 var description = assemblyFromAssemblyName[assemblyName];
527                 Logger.LogAs(this, LogLevel.Warning, "Assembly {0} is hidden by one located in {1} (same simple name {2}).",
528                          path, description.Path, assemblyName);
529             }
530             var types = new List<TypeDefinition>();
531             foreach(var module in assembly.Modules)
532             {
533                 // we add the assembly's directory to the resolve directory - also all known directories
534                 knownDirectories.Add(Path.GetDirectoryName(path));
535                 var defaultAssemblyResolver = ((DefaultAssemblyResolver)module.AssemblyResolver);
536                 foreach(var directory in knownDirectories)
537                 {
538                     defaultAssemblyResolver.AddSearchDirectory(directory);
539                 }
540                 types.AddRange(module.GetTypes());
541             }
542 
543             var hidePluginsFromThisAssembly = false;
544 
545             // It happens that `entryAssembly` is null, e.g., when running tests inside MD.
546             // In such case we don't care about hiding plugins, so we just skip this mechanism (as this is the simples solution to the NRE problem).
547             var entryAssembly = Assembly.GetEntryAssembly();
548             if(entryAssembly != null && IsReferenced(entryAssembly, assembly.FullName))
549             {
550                 Logger.LogAs(this, LogLevel.Noisy, "Plugins from this assembly {0} will be hidden as it is explicitly referenced.", assembly.FullName);
551                 hidePluginsFromThisAssembly = true;
552             }
553 
554             foreach(var type in types)
555             {
556             #if NET
557                 if(type.Interfaces.Any(i => ResolveInner(i.InterfaceType)?.GetFullNameOfMember() == typeof(IPeripheral).FullName))
558             #else
559                 if(type.Interfaces.Any(i => ResolveInner(i)?.GetFullNameOfMember() == typeof(IPeripheral).FullName))
560             #endif
561                 {
562                     Logger.LogAs(this, LogLevel.Noisy, "Peripheral type {0} found.", type.Resolve().GetFullNameOfMember());
563                     foundPeripherals.Add(type);
564                 }
565 
566                 if(type.CustomAttributes.Any(x => ResolveInner(x.AttributeType)?.GetFullNameOfMember() == typeof(PluginAttribute).FullName))
567                 {
568                     Logger.LogAs(this, LogLevel.Noisy, "Plugin type {0} found.", type.Resolve().GetFullNameOfMember());
569                     try
570                     {
571                         foundPlugins.Add(new PluginDescriptor(type, hidePluginsFromThisAssembly));
572                     }
573                     catch(Exception e)
574                     {
575                         //This may happend due to, e.g., version parsing error. The plugin is ignored.
576                         Logger.LogAs(this, LogLevel.Error, "Plugin type {0} loading error: {1}.", type.GetFullNameOfMember(), e.Message);
577                     }
578                 }
579 
580                 if(IsAutoLoadType(type))
581                 {
582                     var loadedType = GetTypeWithLazyLoad(type.GetFullNameOfMember(), assembly.FullName, path);
583                     lock(autoLoadedTypeLocker)
584                     {
585                         autoLoadedTypes.Add(loadedType);
586                     }
587                     var autoLoadedType = autoLoadedTypeEvent;
588                     if(autoLoadedType != null)
589                     {
590                         autoLoadedType(loadedType);
591                     }
592                     continue;
593                 }
594                 if(!TryExtractExtensionMethods(type, out var extractedMethods) && !IsInterestingType(type))
595                 {
596                     continue;
597                 }
598                 // type is interesting, we'll put it into our dictionaries
599                 // after conflicts checking
600                 var fullName = type.GetFullNameOfMember();
601                 var newAssemblyDescription = GetAssemblyDescription(assembly.FullName, path);
602                 Logger.LogAs(this, LogLevel.Noisy, "Type {0} added.", fullName);
603                 if(assembliesFromTypeName.ContainsKey(fullName))
604                 {
605                     assembliesFromTypeName[fullName].Add(newAssemblyDescription);
606                     continue;
607                 }
608                 if(assemblyFromTypeName.ContainsKey(fullName))
609                 {
610                     throw new InvalidOperationException($"Tried to load assembly '{fullName}' that has been already loaded. Aborting operation.");
611                 }
612                 assemblyFromTypeName.Add(fullName, newAssemblyDescription);
613                 if(extractedMethods != null)
614                 {
615                     ProcessExtractedExtensionMethods(extractedMethods);
616                 }
617             }
618 
619             return true;
620         }
621 
ProcessExtractedExtensionMethods(Dictionary<string, HashSet<MethodDescription>> methodsToStore)622         private void ProcessExtractedExtensionMethods(Dictionary<string, HashSet<MethodDescription>> methodsToStore)
623         {
624             foreach(var item in methodsToStore)
625             {
626                 if(extensionMethodsTraceFromTypeFullName.ContainsKey(item.Key))
627                 {
628                     foreach(var method in item.Value)
629                     {
630                         extensionMethodsTraceFromTypeFullName[item.Key].Add(method);
631                     }
632                 }
633                 else
634                 {
635                     extensionMethodsTraceFromTypeFullName.Add(item.Key, item.Value);
636                 }
637             }
638         }
639 
IsAutoLoadType(TypeDefinition type)640         private bool IsAutoLoadType(TypeDefinition type)
641         {
642         #if NET
643             var isAutoLoad = type.Interfaces.Select(x => x.InterfaceType.GetFullNameOfMember()).Contains(typeof(IAutoLoadType).FullName);
644         #else
645             var isAutoLoad = type.Interfaces.Select(x => x.GetFullNameOfMember()).Contains(typeof(IAutoLoadType).FullName);
646         #endif
647             if(isAutoLoad)
648             {
649                 return true;
650             }
651             var resolved = ResolveBaseType(type);
652             if(resolved == null)
653             {
654                 return false;
655             }
656             return IsAutoLoadType(resolved);
657         }
658 
ResolveBaseType(TypeDefinition type)659         private TypeDefinition ResolveBaseType(TypeDefinition type)
660         {
661             return (type.BaseType == null)
662                 ? null
663                 : ResolveInner(type.BaseType);
664         }
665 
IsInterestingType(TypeReference type)666         private bool IsInterestingType(TypeReference type)
667         {
668             return interestingNamespacePrefixes.Any(x => type.Namespace.StartsWith(x));
669         }
670 
GetAssemblyDescription(string fullName, string path)671         private AssemblyDescription GetAssemblyDescription(string fullName, string path)
672         {
673             // maybe we already have one like that (interning)
674             if(assemblyFromAssemblyPath.ContainsKey(path))
675             {
676                 return assemblyFromAssemblyPath[path];
677             }
678             var description = new AssemblyDescription(fullName, path);
679             assemblyFromAssemblyPath.Add(path, description);
680             return description;
681         }
682 
ClearExtensionMethodsCache()683         private void ClearExtensionMethodsCache()
684         {
685             extensionMethodsFromThisType.Clear(); // to be consistent with string dictionary
686         }
687 
GetMethodSignature(MethodDefinition definition)688         private static string GetMethodSignature(MethodDefinition definition)
689         {
690             return definition.Parameters.Select(x => x.ParameterType.GetFullNameOfMember()).Aggregate((x, y) => x + "," + y);
691         }
692 
GetMethodSignature(MethodInfo info)693         private static string GetMethodSignature(MethodInfo info)
694         {
695             return info.GetParameters().Select(x => GetSimpleFullTypeName(x.ParameterType)).Aggregate((x, y) => x + "," + y);
696         }
697 
GetSimpleFullTypeName(Type type)698         private static string GetSimpleFullTypeName(Type type)
699         {
700             if(!type.IsGenericType)
701             {
702                 return type.IsGenericParameter ? type.ToString() : type.FullName;
703             }
704             var result = string.Format("{0}<{1}>", type.GetGenericTypeDefinition().FullName,
705                                        type.GetGenericArguments().Select(x => GetSimpleFullTypeName(x)).Aggregate((x, y) => x + "," + y));
706             return result;
707         }
708 
GetTypeCount()709         private int GetTypeCount()
710         {
711             lock(dictSync)
712             {
713                 return assembliesFromTypeName.Count + assemblyFromTypeName.Count;
714             }
715         }
716 
717         private readonly Dictionary<string, AssemblyDescription> assemblyFromTypeName;
718         private readonly Dictionary<string, AssemblyDescription> assemblyFromAssemblyName;
719         private readonly Dictionary<string, List<AssemblyDescription>> assembliesFromTypeName;
720         private readonly Dictionary<string, HashSet<MethodDescription>> extensionMethodsTraceFromTypeFullName;
721         private readonly Dictionary<Type, MethodInfo[]> extensionMethodsFromThisType;
722         private Dictionary<string, AssemblyDescription> assemblyFromAssemblyPath;
723         private readonly object dictSync;
724         private readonly HashSet<string> knownDirectories;
725 
726         private readonly List<TypeDefinition> foundPeripherals;
727         private readonly List<PluginDescriptor> foundPlugins;
728 
729         private readonly bool isBundled;
730 
731         private static string[] interestingNamespacePrefixes = new []
732         {
733             "Antmicro.Renode",
734             "NetMQ",
735         };
736 
737         // This list filters out assemblies that are known not to be interesting for TypeManager.
738         // It has to be manualy catered for, but it shaves about 400ms from the startup time on mono and 2s on NET.
739         private static string[] assemblyBlacklist = new []
740         {
741             "AntShell.dll",
742             "AtkSharp.dll",
743             "BigGustave.dll",
744             "BitMiracle.LibJpeg.NET.dll",
745             "CairoSharp.dll",
746             "CookComputing.XmlRpcV2.dll",
747             "crypto.dll",
748             "CxxDemangler.dll",
749             "Dynamitey.dll",
750             "ELFSharp.dll",
751             "FdtSharp.dll",
752             "GdkSharp.dll",
753             "GioSharp.dll",
754             "GLibSharp.dll",
755             "GtkSharp.dll",
756             "IronPython.dll",
757             "IronPython.Modules.dll",
758             "IronPython.SQLite.dll",
759             "IronPython.StdLib.dll",
760             "IronPython.Wpf.dll",
761             "K4os.Compression.LZ4.dll",
762             "libtftp.dll",
763             "LZ4.dll",
764             "mcs.dll",
765             "Microsoft.Dynamic.dll",
766             "Microsoft.Scripting.dll",
767             "Microsoft.Scripting.Metadata.dll",
768             "Migrant.dll",
769             "Mono.Cecil.dll",
770             "Mono.Cecil.Mdb.dll",
771             "Mono.Cecil.Pdb.dll",
772             "Mono.Cecil.Rocks.dll",
773             "NaCl.dll",
774             "Newtonsoft.Json.dll",
775             "Nini.dll",
776             "NuGet.Frameworks.dll",
777             "nunit.engine.api.dll",
778             "nunit.engine.core.dll",
779             "nunit.engine.dll",
780             "nunit.framework.dll",
781             "NUnit3.TestAdapter.dll",
782             "OptionsParser.dll",
783             "PacketDotNet.dll",
784             "PangoSharp.dll",
785             "protobuf-net.dll",
786             "Sprache.dll",
787             "TermSharp.dll",
788             "testhost.dll",
789             "Xwt.dll",
790             "Xwt.Gtk.dll",
791             "Xwt.Gtk3.dll",
792             "Xwt.WPF.dll",
793             // Exclude from analysis all "Microsoft" and "System" assemblies.
794             "Microsoft.",
795             "System.",
796         };
797 
798         private class AssemblyDescription
799         {
800             public readonly string Path;
801 
802             public readonly string FullName;
803 
AssemblyDescription(string fullName, string path)804             public AssemblyDescription(string fullName, string path)
805             {
806                 FullName = fullName;
807                 Path = path;
808             }
809 
Equals(object obj)810             public override bool Equals(object obj)
811             {
812                 var other = obj as AssemblyDescription;
813                 if(other == null)
814                 {
815                     return false;
816                 }
817                 return other.Path == Path && other.FullName == FullName;
818             }
819 
GetHashCode()820             public override int GetHashCode()
821             {
822                 return Path.GetHashCode();
823             }
824         }
825 
826         private struct MethodDescription
827         {
828             public readonly string TypeFullName;
829             public readonly string Name;
830             public readonly string Signature;
831             public readonly bool IsOverloaded;
832 
MethodDescriptionAntmicro.Renode.Utilities.TypeManager.MethodDescription833             public MethodDescription(string typeFullName, string name, string signature, bool overloaded)
834             {
835                 TypeFullName = typeFullName;
836                 Name = name;
837                 Signature = signature;
838                 IsOverloaded = overloaded;
839             }
840         }
841     }
842 }
843