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