Ei kuvausta
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AssemblyLoader.cs 34KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Text;
  6. #if BURST_COMPILER_SHARED
  7. using Burst.Compiler.IL;
  8. using Burst.Compiler.IL.DebugInfo;
  9. using Burst.Compiler.IL.Diagnostics;
  10. using Burst.Compiler.IL.Helpers;
  11. #endif
  12. using Mono.Cecil;
  13. using Mono.Cecil.Cil;
  14. using Mono.Cecil.Pdb;
  15. namespace zzzUnity.Burst.CodeGen
  16. {
  17. /// <summary>
  18. /// Provides an assembly loader with caching depending on the LastWriteTime of the assembly file.
  19. /// </summary>
  20. /// <remarks>
  21. /// This class is not thread safe. It needs to be protected outside.
  22. /// </remarks>
  23. #if BURST_COMPILER_SHARED
  24. public
  25. #else
  26. internal
  27. #endif
  28. class AssemblyLoader : BaseAssemblyResolver
  29. {
  30. private readonly Dictionary<string, CacheAssemblyEntry> _nameToEntry;
  31. private readonly Dictionary<string, CacheAssemblyEntry> _fileToEntry;
  32. #if BURST_COMPILER_SHARED
  33. private readonly AssemblyWatcherManager _assemblyWatcherManager;
  34. private readonly HashSet<string> _assemblyWatcherChangedFolders;
  35. public readonly PortablePdbCache PdbCacheReference;
  36. public AssemblyLoader(PortablePdbCache instance, AssemblyWatcherManager assemblyWatcherManager = null)
  37. #else
  38. public AssemblyLoader()
  39. #endif
  40. {
  41. _fileToEntry = new Dictionary<string, CacheAssemblyEntry>(StringComparer.Ordinal);
  42. _nameToEntry = new Dictionary<string, CacheAssemblyEntry>(StringComparer.Ordinal);
  43. #if BURST_COMPILER_SHARED
  44. _assemblyWatcherManager = assemblyWatcherManager;
  45. if (_assemblyWatcherManager != null)
  46. {
  47. _assemblyWatcherManager.OnFolderChanged += OnAssemblyWatcherFolderChanged;
  48. }
  49. _assemblyWatcherChangedFolders = new HashSet<string>();
  50. PdbCacheReference = instance ?? throw new ArgumentException("instance must point to a valid PortablePdbCache instance");
  51. #endif
  52. // We remove all setup by Cecil by default (it adds '.' and 'bin')
  53. ClearSearchDirectories();
  54. LoadDebugSymbols = false; // We don't bother loading the symbols by default now, since we use SRM to handle symbols in a more thread safe manner
  55. // this is to maintain compatibility with the patch-assemblies path (see BclApp.cs), used by dots runtime
  56. }
  57. #if BURST_COMPILER_SHARED
  58. private void OnAssemblyWatcherFolderChanged(AssemblyWatcherEventArgs args)
  59. {
  60. lock (_assemblyWatcherChangedFolders)
  61. {
  62. OnLogDebug?.Invoke(LogMessageType.Debug, $"Folder changed: {args.ChangedFolder}");
  63. _assemblyWatcherChangedFolders.Add(args.ChangedFolder);
  64. }
  65. }
  66. #endif
  67. public bool IsDebugging { get; set; }
  68. public bool LoadDebugSymbols { get; set; }
  69. #if BURST_COMPILER_SHARED
  70. private bool CheckAssemblyDirty => _assemblyWatcherManager != null;
  71. #endif
  72. public Action<LogMessageType, string> OnLogDebug { get; set; }
  73. internal Action<AssemblyNameReferenceAndPath> OnResolve { get; set; }
  74. internal Action<AssemblyNameReference, Action<LogMessageType, string>> OnDirty { get; set; }
  75. public void Clear()
  76. {
  77. foreach (var entry in this._nameToEntry.Values)
  78. entry.Definition.Dispose();
  79. _nameToEntry.Clear();
  80. _fileToEntry.Clear();
  81. }
  82. public bool EnsureSearchDirectories(string[] folders)
  83. {
  84. for (var i = 0; i < folders.Length; i++)
  85. {
  86. folders[i] = NormalizeFilePath(folders[i]);
  87. }
  88. // If the existing search directories are the same as the ones we've been passed,
  89. // then there's nothing to do.
  90. #if BURST_COMPILER_SHARED
  91. var existingSearchDirectories = HashSetPool<string>.Get();
  92. existingSearchDirectories.UnionWith(GetSearchDirectories());
  93. var newSearchDirectories = HashSetPool<string>.Get();
  94. newSearchDirectories.UnionWith(folders);
  95. #else
  96. var existingSearchDirectories = new HashSet<string>(GetSearchDirectories());
  97. var newSearchDirectories = new HashSet<string>(folders);
  98. #endif
  99. try
  100. {
  101. if (existingSearchDirectories.SetEquals(newSearchDirectories))
  102. {
  103. return true;
  104. }
  105. // Otherwise, reset the search directories.
  106. ClearSearchDirectories();
  107. foreach (var path in folders)
  108. {
  109. if (Directory.Exists(path))
  110. {
  111. base.AddSearchDirectory(path);
  112. }
  113. else
  114. {
  115. //Log(LogMessageType.Warning, $"The assembly search path `{path}` does not exist");
  116. newSearchDirectories.Remove(path);
  117. }
  118. }
  119. #if BURST_COMPILER_SHARED
  120. _assemblyWatcherManager?.UpdateFolders(newSearchDirectories);
  121. #endif
  122. }
  123. finally
  124. {
  125. #if BURST_COMPILER_SHARED
  126. HashSetPool<string>.Return(newSearchDirectories);
  127. HashSetPool<string>.Return(existingSearchDirectories);
  128. #endif
  129. }
  130. return false;
  131. }
  132. public void ClearSearchDirectories()
  133. {
  134. foreach (var dir in GetSearchDirectories())
  135. {
  136. RemoveSearchDirectory(dir);
  137. }
  138. }
  139. public AssemblyDefinition LoadFromStream(Stream peStream, Stream pdbStream = null, ISymbolReaderProvider customSymbolReader=null)
  140. {
  141. peStream.Position = 0;
  142. if (pdbStream != null)
  143. {
  144. pdbStream.Position = 0;
  145. }
  146. var readerParameters = CreateReaderParameters();
  147. if (customSymbolReader != null)
  148. {
  149. readerParameters.ReadSymbols = true;
  150. readerParameters.SymbolReaderProvider = customSymbolReader;
  151. }
  152. readerParameters.ReadingMode = ReadingMode.Deferred;
  153. try
  154. {
  155. readerParameters.SymbolStream = pdbStream;
  156. return AssemblyDefinition.ReadAssembly(peStream, readerParameters);
  157. }
  158. catch
  159. {
  160. readerParameters.ReadSymbols = false;
  161. readerParameters.SymbolStream = null;
  162. peStream.Position = 0;
  163. if (pdbStream != null)
  164. {
  165. pdbStream.Position = 0;
  166. }
  167. return AssemblyDefinition.ReadAssembly(peStream, readerParameters);
  168. }
  169. }
  170. public override AssemblyDefinition Resolve(AssemblyNameReference name)
  171. {
  172. CacheAssemblyEntry cacheEntry;
  173. if (this._nameToEntry.TryGetValue(name.FullName, out cacheEntry))
  174. {
  175. if (!IsCacheEntryDirtyAndNotify(cacheEntry))
  176. {
  177. OnResolve?.Invoke(new AssemblyNameReferenceAndPath(name, cacheEntry.FilePath));
  178. return cacheEntry.Definition;
  179. }
  180. RemoveEntryFromCache(cacheEntry.Name, cacheEntry);
  181. }
  182. var readerParameters = CreateReaderParameters();
  183. readerParameters.ReadingMode = ReadingMode.Deferred;
  184. AssemblyDefinition assemblyDefinition;
  185. try
  186. {
  187. assemblyDefinition = this.Resolve(name, readerParameters);
  188. }
  189. catch
  190. {
  191. if (readerParameters.ReadSymbols == true)
  192. {
  193. // Attempt to load without symbols
  194. readerParameters.ReadSymbols = false;
  195. assemblyDefinition = this.Resolve(name, readerParameters);
  196. }
  197. else
  198. {
  199. throw;
  200. }
  201. }
  202. RegisterAssembly(name, assemblyDefinition);
  203. OnResolve?.Invoke(new AssemblyNameReferenceAndPath(name, _nameToEntry[name.FullName].FilePath));
  204. return assemblyDefinition;
  205. }
  206. public bool TryGetFullPath(AssemblyNameReference name, out string fullPath)
  207. {
  208. try
  209. {
  210. // We don't care about the return value - we just want to ensure
  211. // that _nameToEntry has the correct cache entry.
  212. Resolve(name);
  213. }
  214. catch (AssemblyResolutionException)
  215. {
  216. fullPath = null;
  217. return false;
  218. }
  219. var cacheEntry = _nameToEntry[name.FullName];
  220. fullPath = cacheEntry.FilePath;
  221. return true;
  222. }
  223. public string GetFullPath(AssemblyNameReference name)
  224. {
  225. try
  226. {
  227. // We don't care about the return value - we just want to ensure
  228. // that _nameToEntry has the correct cache entry.
  229. Resolve(name);
  230. }
  231. catch (AssemblyResolutionException ex)
  232. {
  233. throw new Exception("Unable to resolve assembly using search directories: " + Environment.NewLine + string.Join(Environment.NewLine, GetSearchDirectories()), ex);
  234. }
  235. var cacheEntry = _nameToEntry[name.FullName];
  236. return cacheEntry.FilePath;
  237. }
  238. private bool IsCacheEntryDirtyAndNotify(CacheAssemblyEntry entry)
  239. {
  240. #if BURST_COMPILER_SHARED
  241. // By default, we don't check assembly dirtiness as it is requiring a costly kernel context switch with the filesystem
  242. // and hurting significantly the performance for btests
  243. if (!CheckAssemblyDirty)
  244. {
  245. return false;
  246. }
  247. #endif
  248. if (entry.FileTimeVerified) return false;
  249. var lastWriteTime = File.GetLastWriteTime(entry.FilePath);
  250. // GetLastWriteTime returns 01/01/1601 if the file doesn't exist.
  251. var fileDoesNotExistTime = DateTime.FromFileTime(0);
  252. var isDirty = lastWriteTime == fileDoesNotExistTime || lastWriteTime > entry.FileTime;
  253. entry.FileTimeVerified = true;
  254. if (IsDebugging)
  255. {
  256. OnLogDebug?.Invoke(LogMessageType.Debug, $"Checking Assembly file timestamp {entry.FilePath} Cached: `{DateTimeToStringPrecise(entry.FileTime)}` OnDisk: `{DateTimeToStringPrecise(lastWriteTime)}` => {(isDirty?"DIRTY" : "Not dirty")}");
  257. }
  258. if (isDirty)
  259. {
  260. OnDirty?.Invoke(entry.Definition.Name, OnLogDebug);
  261. return true;
  262. }
  263. return false;
  264. }
  265. private static string DateTimeToStringPrecise(DateTime datetime)
  266. {
  267. return datetime.ToString("yyyy-MM-dd HH:mm:ss.fff");
  268. }
  269. public string DumpCache()
  270. {
  271. var builder = new StringBuilder();
  272. if (_nameToEntry.Count > 0)
  273. {
  274. foreach (var cacheAssemblyEntry in _nameToEntry)
  275. {
  276. builder.AppendLine($"- {cacheAssemblyEntry.Value.ToString()}");
  277. }
  278. }
  279. else
  280. {
  281. builder.AppendLine("- [No assemblies in AssemblyLoader cache]");
  282. }
  283. return builder.ToString();
  284. }
  285. public void UpdateCache()
  286. {
  287. #if BURST_COMPILER_SHARED
  288. if (!CheckAssemblyDirty) return;
  289. var anythingChanged = false;
  290. lock (_assemblyWatcherChangedFolders)
  291. {
  292. foreach (var changedFolder in _assemblyWatcherChangedFolders)
  293. {
  294. OnLogDebug?.Invoke(LogMessageType.Debug, $"Folder changed: `{changedFolder}`");
  295. foreach (var key in _fileToEntry.Keys)
  296. {
  297. if (key.StartsWith(changedFolder, StringComparison.InvariantCultureIgnoreCase))
  298. {
  299. var entry = _fileToEntry[key];
  300. entry.FileTimeVerified = false;
  301. OnLogDebug?.Invoke(LogMessageType.Debug, $"Assembly marked dirty: `{key}`");
  302. anythingChanged = true;
  303. }
  304. }
  305. }
  306. _assemblyWatcherChangedFolders.Clear();
  307. foreach (var key in _fileToEntry.Keys)
  308. {
  309. var entry = _fileToEntry[key];
  310. if (entry.FileTimeVerified)
  311. {
  312. OnLogDebug?.Invoke(LogMessageType.Debug, $"Assembly not dirty: `{key}`");
  313. }
  314. }
  315. }
  316. if (!anythingChanged)
  317. {
  318. return;
  319. }
  320. var keys = _nameToEntry.Keys.ToArray();
  321. foreach (var key in keys)
  322. {
  323. var entry = _nameToEntry[key];
  324. if (IsCacheEntryDirtyAndNotify(entry))
  325. {
  326. RemoveEntryFromCache(key, entry);
  327. }
  328. }
  329. #endif
  330. }
  331. private void RemoveEntryFromCache(string entryKey, CacheAssemblyEntry entry)
  332. {
  333. #if BURST_COMPILER_SHARED
  334. PdbCacheReference.RemoveEntry(entry.Definition, OnLogDebug);
  335. #endif
  336. _nameToEntry.Remove(entryKey);
  337. _fileToEntry.Remove(entry.FilePath);
  338. }
  339. public new void AddSearchDirectory(string directory)
  340. {
  341. if (!GetSearchDirectories().Contains(directory))
  342. {
  343. base.AddSearchDirectory(directory);
  344. }
  345. }
  346. /// <summary>
  347. /// Loads the specified assembly.
  348. /// </summary>
  349. /// <param name="assemblyLocation">The assembly location.</param>
  350. /// <param name="useSymbols">if set to <c>true</c> load and use the pdb symbols.</param>
  351. /// <exception cref="ArgumentNullException">assemblyLocation</exception>
  352. /// <returns>The loaded Assembly definition.</returns>
  353. public AssemblyDefinition LoadFromFile(string assemblyLocation)
  354. {
  355. if (assemblyLocation == null) throw new ArgumentNullException(nameof(assemblyLocation));
  356. // If the file was already loaded, don't try to load it
  357. CacheAssemblyEntry cacheEntry;
  358. assemblyLocation = NormalizeFilePath(assemblyLocation);
  359. if (this._fileToEntry.TryGetValue(assemblyLocation, out cacheEntry))
  360. {
  361. if (!IsCacheEntryDirtyAndNotify(cacheEntry))
  362. {
  363. return cacheEntry.Definition;
  364. }
  365. RemoveEntryFromCache(cacheEntry.Name, cacheEntry);
  366. }
  367. if (assemblyLocation == null) throw new ArgumentNullException(nameof(assemblyLocation));
  368. var readerParams = CreateReaderParameters();
  369. AssemblyDefinition assemblyDefinition;
  370. try
  371. {
  372. assemblyDefinition = AssemblyDefinition.ReadAssembly(assemblyLocation, readerParams);
  373. }
  374. catch (Exception)
  375. {
  376. if (readerParams.ReadSymbols == true)
  377. {
  378. // Attempt to load without symbols
  379. readerParams.ReadSymbols = false;
  380. assemblyDefinition = AssemblyDefinition.ReadAssembly(assemblyLocation, readerParams);
  381. }
  382. else
  383. {
  384. throw;
  385. }
  386. }
  387. // AssemblyDefinition.Load For some reason, assemblyLoader doesn't cache properly
  388. RegisterAssembly(assemblyDefinition.Name, assemblyDefinition);
  389. return assemblyDefinition;
  390. }
  391. private ReaderParameters CreateReaderParameters()
  392. {
  393. var readerParams = new ReaderParameters
  394. {
  395. InMemory = true,
  396. AssemblyResolver = this,
  397. MetadataResolver = new CustomMetadataResolver(this),
  398. ReadSymbols = LoadDebugSymbols // We no longer use cecil to read symbol information, prefering SRM thread safe methods, so I`m being explicit here in case the default changes
  399. };
  400. if (LoadDebugSymbols)
  401. {
  402. readerParams.SymbolReaderProvider = new CustomSymbolReaderProvider(null);
  403. }
  404. return readerParams;
  405. }
  406. #if BURST_COMPILER_SHARED
  407. /// <summary>
  408. /// Resolves a cecil <see cref="MethodReference"/> from a <see cref="MethodReferenceString"/>
  409. /// </summary>
  410. /// <param name="methodReferenceString">A method reference string</param>
  411. /// <returns>A cecil <see cref="MethodReference"/></returns>
  412. public MethodReference Resolve(MethodReferenceString methodReferenceString)
  413. {
  414. if (methodReferenceString == null) throw new ArgumentNullException(nameof(methodReferenceString));
  415. var typeReference = Resolve(methodReferenceString.DeclaringType);
  416. var typeDefinition = typeReference.StrictResolve();
  417. // Initial capacity 1 because that is overwhelmingly the likely outcome.
  418. var methods = new List<MethodDefinition>(1);
  419. foreach (var m in typeDefinition.Methods)
  420. {
  421. if (m.Name != methodReferenceString.Name)
  422. {
  423. continue;
  424. }
  425. if (m.Parameters.Count != methodReferenceString.ParameterTypes.Count)
  426. {
  427. continue;
  428. }
  429. methods.Add(m);
  430. }
  431. // We expect to match at least one method
  432. if (methods.Count == 0)
  433. {
  434. throw new InvalidOperationException($"Unable to find the method `{methodReferenceString}` from type `{typeReference}`");
  435. }
  436. // If we have more than one match we need to do a more expensive re-evaluation of the methods to figure out which types they use match.
  437. if (methods.Count > 1)
  438. {
  439. MethodDefinition methodToUse = null;
  440. foreach (var m in methods)
  441. {
  442. var count = m.Parameters.Count;
  443. var match = true;
  444. for (int i = 0; i < count; i++)
  445. {
  446. var parameterTypeFullName = methodReferenceString.ParameterTypes[i].ToString(ReferenceStringFormatOptions.None).Replace("+", "/").Replace("[[", "<").Replace("]]", ">");
  447. if (m.Parameters[i].ParameterType.FullName != parameterTypeFullName)
  448. {
  449. match = false;
  450. break;
  451. }
  452. }
  453. if (match)
  454. {
  455. methodToUse = m;
  456. break;
  457. }
  458. }
  459. if (methodToUse == null)
  460. {
  461. throw new Exception($"Unable to resolve the method `{methodReferenceString}` from type `{typeReference}` to a single method from set of {methods.Count} methods");
  462. }
  463. methods.Clear();
  464. methods.Add(methodToUse);
  465. }
  466. var method = methods[0];
  467. var methodReference = new MethodReference(method.Name, method.ReturnType, typeReference);
  468. foreach (var param in method.Parameters)
  469. {
  470. methodReference.Parameters.Add(new ParameterDefinition(param.Name, param.Attributes, param.ParameterType));
  471. }
  472. return methodReference;
  473. }
  474. /// <summary>
  475. /// Resolves a Cecil <see cref="MethodReference"/> from a reflection <see cref="MethodInfo"/>.
  476. /// Only used for testing purposes.
  477. /// </summary>
  478. /// <remarks>
  479. /// The <see cref="AssemblyDefinition"/> loaded is cached.
  480. /// </remarks>
  481. public MethodReference Resolve(System.Reflection.MethodInfo method)
  482. {
  483. if (method == null) throw new ArgumentNullException(nameof(method));
  484. if (method.DeclaringType == null) throw new NotSupportedException($"The method `{method}` must have a declaring type");
  485. var thisMethodAssemblyLocation = method.DeclaringType.Assembly.Location;
  486. var assemblyLocation = thisMethodAssemblyLocation;
  487. if (assemblyLocation == null)
  488. {
  489. throw new ArgumentException($"Cannot determine the assembly location for the method `{method}`", nameof(method));
  490. }
  491. if (!File.Exists(assemblyLocation))
  492. {
  493. throw new FileNotFoundException($"The assembly [{assemblyLocation}] was not found");
  494. }
  495. AssemblyDefinition definition;
  496. // Force to load a mono compiled assembly (used on Windows to cross tests between .NET CLR and Mono CLR)
  497. // For convenience, we add automatically the search directory for the assembly path
  498. // based on the method being compiled and the generic parameters
  499. var assemblyLocationFolder = Path.GetDirectoryName(assemblyLocation);
  500. AddSearchDirectory(assemblyLocationFolder);
  501. if (assemblyLocation != thisMethodAssemblyLocation)
  502. {
  503. assemblyLocationFolder = Path.GetDirectoryName(thisMethodAssemblyLocation);
  504. AddSearchDirectory(assemblyLocationFolder);
  505. }
  506. // Helper loop to extract assembly path locations from generic parameters (on declaring type and method)
  507. // TODO: this is not entirely correct, we would have to inspect deep nested generics to really support this
  508. foreach (var genericArgument in method.DeclaringType.GetGenericArguments().Concat(method.GetGenericArguments()))
  509. {
  510. var location = Path.GetDirectoryName(genericArgument.Assembly.Location);
  511. AddSearchDirectory(location);
  512. }
  513. var assemblyName = method.DeclaringType.Assembly.GetName();
  514. definition = Resolve(new AssemblyNameReference(assemblyName.Name, assemblyName.Version));
  515. // Resolve the Cecil MethodReference from the System.Reflection.MethodInfo
  516. var methodReference = definition.MainModule.ImportReference(method);
  517. // NOTE: this is a workaround for ref readonly return where Cecil is actually transforming it to an InAttribute as a modreq
  518. // while the C# modreq is actually `IsReadOnlyAttribute`
  519. // So in that case we transform the return type with the modreq expected by Cecil
  520. // otherwise the following methodReference.Resolve() would fail finding the method definition
  521. // IsReadOnlyAttribute Not Available until netstandard 2.1
  522. bool hasReadOnlyAttribute = false;
  523. foreach (var attr in method.ReturnTypeCustomAttributes.GetCustomAttributes(true))
  524. {
  525. if (attr.ToString() == "System.Runtime.CompilerServices.IsReadOnlyAttribute")
  526. {
  527. hasReadOnlyAttribute = true;
  528. break;
  529. }
  530. }
  531. if (hasReadOnlyAttribute)
  532. {
  533. var typeRef = definition.MainModule.ImportReference(typeof(System.Runtime.InteropServices.InAttribute));
  534. methodReference.ReturnType = new RequiredModifierType(typeRef, methodReference.ReturnType);
  535. }
  536. if (methodReference?.Resolve() == null)
  537. {
  538. throw new InvalidOperationException($"Unable to find method `{methodReference}` from assembly location `{assemblyLocation}`");
  539. }
  540. return methodReference;
  541. }
  542. /// <summary>
  543. /// Resolves a cecil <see cref="TypeReference"/> from a <see cref="SimpleTypeReferenceString"/>
  544. /// </summary>
  545. /// <param name="simpleTypeReferenceString">A type reference string</param>
  546. /// <returns>A cecil <see cref="TypeReference"/></returns>
  547. public TypeReference Resolve(SimpleTypeReferenceString simpleTypeReferenceString)
  548. {
  549. if (simpleTypeReferenceString == null) throw new ArgumentNullException(nameof(simpleTypeReferenceString));
  550. var assemblyOfMainType = Resolve(simpleTypeReferenceString.Assembly);
  551. var subTypes = simpleTypeReferenceString.FullName.Split(new char[] {'+'});
  552. Guard.Assert(subTypes.Length >= 1);
  553. var typeReference = (TypeReference)assemblyOfMainType.MainModule.GetType(subTypes[0]);
  554. if (typeReference == null)
  555. {
  556. throw new InvalidOperationException(
  557. $"Unable to find type `{simpleTypeReferenceString.FullName}` from assembly `{simpleTypeReferenceString.Assembly}`");
  558. }
  559. for (var i = 1; i < subTypes.Length; i++)
  560. {
  561. var subType = subTypes[i];
  562. var definition = typeReference.StrictResolve();
  563. var nestedDefinition = definition.NestedTypes.FirstOrDefault(nestedType => nestedType.Name == subType);
  564. if (nestedDefinition == null)
  565. {
  566. throw new InvalidOperationException($"Unable to find nested type `{subType}` from typename `{simpleTypeReferenceString.FullName}` assembly `{simpleTypeReferenceString.Assembly}`");
  567. }
  568. typeReference = nestedDefinition;
  569. }
  570. var generic = simpleTypeReferenceString as GenericInstanceTypeReferenceString;
  571. if (generic != null)
  572. {
  573. var genericType = new GenericInstanceType(typeReference);
  574. foreach (var genericArgType in generic.GenericArguments)
  575. {
  576. var simpleGenericArgType = genericArgType as SimpleTypeReferenceString;
  577. if (simpleGenericArgType == null)
  578. {
  579. throw new InvalidOperationException($"Unable to resolve generic argument `{genericArgType}`");
  580. }
  581. genericType.GenericArguments.Add(Resolve(simpleGenericArgType));
  582. }
  583. typeReference = genericType;
  584. }
  585. return typeReference;
  586. }
  587. #endif
  588. private void RegisterAssembly(AssemblyNameReference name, AssemblyDefinition assembly)
  589. {
  590. if (assembly == null)
  591. throw new ArgumentNullException(nameof(assembly));
  592. string fullName = name.FullName;
  593. var filename = GetAssemblyFileName(assembly);
  594. var entry = new CacheAssemblyEntry(assembly, filename);
  595. _nameToEntry[fullName] = entry;
  596. // Duplicate the entry (mscorlib 2.0.0 can be remapped to 4.0.0, so better cache them both)
  597. _nameToEntry[assembly.Name.FullName] = entry;
  598. _fileToEntry[filename] = entry;
  599. #if BURST_COMPILER_SHARED
  600. PdbCacheReference.AddEntry(assembly, OnLogDebug);
  601. #endif
  602. }
  603. private string GetAssemblyFileName(AssemblyDefinition assembly)
  604. {
  605. string fileName = assembly.MainModule.FileName;
  606. if (fileName == null)
  607. {
  608. throw new InvalidOperationException($"Unable to find original assembly file from {assembly.Name}");
  609. }
  610. return NormalizeFilePath(fileName);
  611. }
  612. protected override void Dispose(bool disposing)
  613. {
  614. #if BURST_COMPILER_SHARED
  615. if (_assemblyWatcherManager != null)
  616. {
  617. _assemblyWatcherManager.OnFolderChanged -= OnAssemblyWatcherFolderChanged;
  618. _assemblyWatcherManager.Dispose();
  619. }
  620. #endif
  621. Clear();
  622. base.Dispose(disposing);
  623. }
  624. private class CacheAssemblyEntry
  625. {
  626. public CacheAssemblyEntry(AssemblyDefinition assemblyDefinition, string filePath)
  627. {
  628. Definition = assemblyDefinition;
  629. FilePath = filePath;
  630. FileTime = File.GetLastWriteTime(filePath);
  631. FileTimeVerified = true;
  632. }
  633. public string Name => Definition.FullName;
  634. public readonly AssemblyDefinition Definition;
  635. public readonly string FilePath;
  636. public readonly DateTime FileTime;
  637. public bool FileTimeVerified { get; set; }
  638. public override string ToString() => $"{Name} => {FilePath} {DateTimeToStringPrecise(FileTime)}";
  639. }
  640. private static string NormalizeFilePath(string path)
  641. {
  642. return Path.GetFullPath(new Uri(path).LocalPath).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
  643. }
  644. private class CustomMetadataResolver : MetadataResolver
  645. {
  646. public CustomMetadataResolver(IAssemblyResolver assemblyResolver) : base(assemblyResolver)
  647. {
  648. }
  649. public override MethodDefinition Resolve(MethodReference method)
  650. {
  651. if (method is MethodDefinition methodDef)
  652. {
  653. return methodDef;
  654. }
  655. if (method.GetElementMethod() is MethodDefinition methodDef2)
  656. {
  657. return methodDef2;
  658. }
  659. return base.Resolve(method);
  660. }
  661. }
  662. /// <summary>
  663. /// Custom implementation of <see cref="ISymbolReaderProvider"/> to:
  664. /// - to load pdb/mdb through a MemoryStream to avoid locking the file on the disk
  665. /// - catch any exceptions while loading the symbols and report them back
  666. /// </summary>
  667. private class CustomSymbolReaderProvider : ISymbolReaderProvider
  668. {
  669. private readonly Action<string, Exception> _logException;
  670. public CustomSymbolReaderProvider(Action<string, Exception> logException)
  671. {
  672. _logException = logException;
  673. }
  674. public ISymbolReader GetSymbolReader(ModuleDefinition module, string fileName)
  675. {
  676. if (string.IsNullOrWhiteSpace(fileName)) return null;
  677. string pdbFileName = fileName;
  678. try
  679. {
  680. fileName = NormalizeFilePath(fileName);
  681. pdbFileName = GetPdbFileName(fileName);
  682. if (File.Exists(pdbFileName))
  683. {
  684. var pdbStream = ReadToMemoryStream(pdbFileName);
  685. if (IsPortablePdb(pdbStream))
  686. return new SafeDebugReaderProvider(new PortablePdbReaderProvider().GetSymbolReader(module, pdbStream));
  687. return new SafeDebugReaderProvider(new NativePdbReaderProvider().GetSymbolReader(module, pdbStream));
  688. }
  689. }
  690. catch (Exception ex) when (_logException != null)
  691. {
  692. _logException?.Invoke($"Unable to load symbol `{pdbFileName}`", ex);
  693. return null;
  694. }
  695. return null;
  696. }
  697. private static MemoryStream ReadToMemoryStream(string filename)
  698. {
  699. return new MemoryStream(File.ReadAllBytes(filename));
  700. }
  701. public ISymbolReader GetSymbolReader(ModuleDefinition module, Stream symbolStream)
  702. {
  703. throw new NotSupportedException();
  704. }
  705. private static string GetPdbFileName(string assemblyFileName)
  706. {
  707. return Path.ChangeExtension(assemblyFileName, ".pdb");
  708. }
  709. private static bool IsPortablePdb(Stream stream)
  710. {
  711. if (stream.Length < 4L)
  712. return false;
  713. long position = stream.Position;
  714. try
  715. {
  716. return (int)new BinaryReader(stream).ReadUInt32() == 1112167234;
  717. }
  718. finally
  719. {
  720. stream.Position = position;
  721. }
  722. }
  723. /// <summary>
  724. /// This class is a wrapper around <see cref="ISymbolReader"/> to protect
  725. /// against failure while trying to read debug information in Mono.Cecil
  726. /// </summary>
  727. private class SafeDebugReaderProvider : ISymbolReader
  728. {
  729. private readonly ISymbolReader _reader;
  730. public SafeDebugReaderProvider(ISymbolReader reader)
  731. {
  732. _reader = reader;
  733. }
  734. public void Dispose()
  735. {
  736. try
  737. {
  738. _reader.Dispose();
  739. }
  740. catch
  741. {
  742. // ignored
  743. }
  744. }
  745. public ISymbolWriterProvider GetWriterProvider()
  746. {
  747. // We are not protecting here as we are not suppose to write to PDBs
  748. return _reader.GetWriterProvider();
  749. }
  750. public bool ProcessDebugHeader(ImageDebugHeader header)
  751. {
  752. try
  753. {
  754. return _reader.ProcessDebugHeader(header);
  755. }
  756. catch
  757. {
  758. // ignored
  759. }
  760. return false;
  761. }
  762. public MethodDebugInformation Read(MethodDefinition method)
  763. {
  764. try
  765. {
  766. return _reader.Read(method);
  767. }
  768. catch
  769. {
  770. // ignored
  771. }
  772. return null;
  773. }
  774. }
  775. }
  776. #if !BURST_COMPILER_SHARED
  777. public enum LogMessageType
  778. {
  779. Debug,
  780. }
  781. #endif
  782. }
  783. /// <summary>
  784. /// This class is a container for keeping an assembly reference and path together
  785. /// </summary>
  786. internal sealed class AssemblyNameReferenceAndPath
  787. {
  788. /// <summary>
  789. /// Get the assembly name reference
  790. /// </summary>
  791. public readonly AssemblyNameReference AssemblyNameReference;
  792. /// <summary>
  793. /// Get the full path to the assembly
  794. /// </summary>
  795. public readonly string FullPath;
  796. internal AssemblyNameReferenceAndPath(AssemblyNameReference assemblyNameReference, string fullPath)
  797. {
  798. AssemblyNameReference = assemblyNameReference;
  799. FullPath = fullPath;
  800. }
  801. }
  802. }