123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931 |
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Text;
- #if BURST_COMPILER_SHARED
- using Burst.Compiler.IL;
- using Burst.Compiler.IL.DebugInfo;
- using Burst.Compiler.IL.Diagnostics;
- using Burst.Compiler.IL.Helpers;
- #endif
- using Mono.Cecil;
- using Mono.Cecil.Cil;
- using Mono.Cecil.Pdb;
-
- namespace zzzUnity.Burst.CodeGen
- {
- /// <summary>
- /// Provides an assembly loader with caching depending on the LastWriteTime of the assembly file.
- /// </summary>
- /// <remarks>
- /// This class is not thread safe. It needs to be protected outside.
- /// </remarks>
- #if BURST_COMPILER_SHARED
- public
- #else
- internal
- #endif
- class AssemblyLoader : BaseAssemblyResolver
- {
- private readonly Dictionary<string, CacheAssemblyEntry> _nameToEntry;
- private readonly Dictionary<string, CacheAssemblyEntry> _fileToEntry;
-
- #if BURST_COMPILER_SHARED
- private readonly AssemblyWatcherManager _assemblyWatcherManager;
- private readonly HashSet<string> _assemblyWatcherChangedFolders;
-
- public readonly PortablePdbCache PdbCacheReference;
-
- public AssemblyLoader(PortablePdbCache instance, AssemblyWatcherManager assemblyWatcherManager = null)
- #else
- public AssemblyLoader()
- #endif
- {
- _fileToEntry = new Dictionary<string, CacheAssemblyEntry>(StringComparer.Ordinal);
- _nameToEntry = new Dictionary<string, CacheAssemblyEntry>(StringComparer.Ordinal);
- #if BURST_COMPILER_SHARED
- _assemblyWatcherManager = assemblyWatcherManager;
- if (_assemblyWatcherManager != null)
- {
- _assemblyWatcherManager.OnFolderChanged += OnAssemblyWatcherFolderChanged;
- }
- _assemblyWatcherChangedFolders = new HashSet<string>();
-
- PdbCacheReference = instance ?? throw new ArgumentException("instance must point to a valid PortablePdbCache instance");
- #endif
-
- // We remove all setup by Cecil by default (it adds '.' and 'bin')
- ClearSearchDirectories();
-
- 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
- // this is to maintain compatibility with the patch-assemblies path (see BclApp.cs), used by dots runtime
- }
-
- #if BURST_COMPILER_SHARED
- private void OnAssemblyWatcherFolderChanged(AssemblyWatcherEventArgs args)
- {
- lock (_assemblyWatcherChangedFolders)
- {
- OnLogDebug?.Invoke(LogMessageType.Debug, $"Folder changed: {args.ChangedFolder}");
- _assemblyWatcherChangedFolders.Add(args.ChangedFolder);
- }
- }
- #endif
-
- public bool IsDebugging { get; set; }
-
- public bool LoadDebugSymbols { get; set; }
-
- #if BURST_COMPILER_SHARED
- private bool CheckAssemblyDirty => _assemblyWatcherManager != null;
- #endif
-
- public Action<LogMessageType, string> OnLogDebug { get; set; }
-
- internal Action<AssemblyNameReferenceAndPath> OnResolve { get; set; }
-
- internal Action<AssemblyNameReference, Action<LogMessageType, string>> OnDirty { get; set; }
-
- public void Clear()
- {
- foreach (var entry in this._nameToEntry.Values)
- entry.Definition.Dispose();
- _nameToEntry.Clear();
- _fileToEntry.Clear();
- }
-
- public bool EnsureSearchDirectories(string[] folders)
- {
- for (var i = 0; i < folders.Length; i++)
- {
- folders[i] = NormalizeFilePath(folders[i]);
- }
-
- // If the existing search directories are the same as the ones we've been passed,
- // then there's nothing to do.
- #if BURST_COMPILER_SHARED
- var existingSearchDirectories = HashSetPool<string>.Get();
- existingSearchDirectories.UnionWith(GetSearchDirectories());
- var newSearchDirectories = HashSetPool<string>.Get();
- newSearchDirectories.UnionWith(folders);
- #else
- var existingSearchDirectories = new HashSet<string>(GetSearchDirectories());
- var newSearchDirectories = new HashSet<string>(folders);
- #endif
-
- try
- {
- if (existingSearchDirectories.SetEquals(newSearchDirectories))
- {
- return true;
- }
-
- // Otherwise, reset the search directories.
- ClearSearchDirectories();
- foreach (var path in folders)
- {
- if (Directory.Exists(path))
- {
- base.AddSearchDirectory(path);
- }
- else
- {
- //Log(LogMessageType.Warning, $"The assembly search path `{path}` does not exist");
- newSearchDirectories.Remove(path);
- }
- }
-
- #if BURST_COMPILER_SHARED
- _assemblyWatcherManager?.UpdateFolders(newSearchDirectories);
- #endif
- }
- finally
- {
- #if BURST_COMPILER_SHARED
- HashSetPool<string>.Return(newSearchDirectories);
- HashSetPool<string>.Return(existingSearchDirectories);
- #endif
- }
-
- return false;
- }
-
- public void ClearSearchDirectories()
- {
- foreach (var dir in GetSearchDirectories())
- {
- RemoveSearchDirectory(dir);
- }
- }
-
- public AssemblyDefinition LoadFromStream(Stream peStream, Stream pdbStream = null, ISymbolReaderProvider customSymbolReader=null)
- {
- peStream.Position = 0;
- if (pdbStream != null)
- {
- pdbStream.Position = 0;
- }
- var readerParameters = CreateReaderParameters();
- if (customSymbolReader != null)
- {
- readerParameters.ReadSymbols = true;
- readerParameters.SymbolReaderProvider = customSymbolReader;
- }
- readerParameters.ReadingMode = ReadingMode.Deferred;
- try
- {
- readerParameters.SymbolStream = pdbStream;
- return AssemblyDefinition.ReadAssembly(peStream, readerParameters);
- }
- catch
- {
- readerParameters.ReadSymbols = false;
- readerParameters.SymbolStream = null;
- peStream.Position = 0;
- if (pdbStream != null)
- {
- pdbStream.Position = 0;
- }
- return AssemblyDefinition.ReadAssembly(peStream, readerParameters);
- }
- }
-
- public override AssemblyDefinition Resolve(AssemblyNameReference name)
- {
- CacheAssemblyEntry cacheEntry;
-
- if (this._nameToEntry.TryGetValue(name.FullName, out cacheEntry))
- {
- if (!IsCacheEntryDirtyAndNotify(cacheEntry))
- {
- OnResolve?.Invoke(new AssemblyNameReferenceAndPath(name, cacheEntry.FilePath));
- return cacheEntry.Definition;
- }
-
- RemoveEntryFromCache(cacheEntry.Name, cacheEntry);
- }
-
- var readerParameters = CreateReaderParameters();
- readerParameters.ReadingMode = ReadingMode.Deferred;
- AssemblyDefinition assemblyDefinition;
-
- try
- {
- assemblyDefinition = this.Resolve(name, readerParameters);
- }
- catch
- {
- if (readerParameters.ReadSymbols == true)
- {
- // Attempt to load without symbols
- readerParameters.ReadSymbols = false;
- assemblyDefinition = this.Resolve(name, readerParameters);
- }
- else
- {
- throw;
- }
- }
-
- RegisterAssembly(name, assemblyDefinition);
- OnResolve?.Invoke(new AssemblyNameReferenceAndPath(name, _nameToEntry[name.FullName].FilePath));
- return assemblyDefinition;
- }
-
- public bool TryGetFullPath(AssemblyNameReference name, out string fullPath)
- {
- try
- {
- // We don't care about the return value - we just want to ensure
- // that _nameToEntry has the correct cache entry.
- Resolve(name);
- }
- catch (AssemblyResolutionException)
- {
- fullPath = null;
- return false;
- }
-
- var cacheEntry = _nameToEntry[name.FullName];
- fullPath = cacheEntry.FilePath;
- return true;
- }
-
- public string GetFullPath(AssemblyNameReference name)
- {
- try
- {
- // We don't care about the return value - we just want to ensure
- // that _nameToEntry has the correct cache entry.
- Resolve(name);
- }
- catch (AssemblyResolutionException ex)
- {
- throw new Exception("Unable to resolve assembly using search directories: " + Environment.NewLine + string.Join(Environment.NewLine, GetSearchDirectories()), ex);
- }
-
- var cacheEntry = _nameToEntry[name.FullName];
- return cacheEntry.FilePath;
- }
-
- private bool IsCacheEntryDirtyAndNotify(CacheAssemblyEntry entry)
- {
- #if BURST_COMPILER_SHARED
- // By default, we don't check assembly dirtiness as it is requiring a costly kernel context switch with the filesystem
- // and hurting significantly the performance for btests
- if (!CheckAssemblyDirty)
- {
- return false;
- }
- #endif
-
- if (entry.FileTimeVerified) return false;
-
- var lastWriteTime = File.GetLastWriteTime(entry.FilePath);
- // GetLastWriteTime returns 01/01/1601 if the file doesn't exist.
- var fileDoesNotExistTime = DateTime.FromFileTime(0);
- var isDirty = lastWriteTime == fileDoesNotExistTime || lastWriteTime > entry.FileTime;
- entry.FileTimeVerified = true;
-
- if (IsDebugging)
- {
- OnLogDebug?.Invoke(LogMessageType.Debug, $"Checking Assembly file timestamp {entry.FilePath} Cached: `{DateTimeToStringPrecise(entry.FileTime)}` OnDisk: `{DateTimeToStringPrecise(lastWriteTime)}` => {(isDirty?"DIRTY" : "Not dirty")}");
- }
-
- if (isDirty)
- {
- OnDirty?.Invoke(entry.Definition.Name, OnLogDebug);
- return true;
- }
- return false;
- }
-
- private static string DateTimeToStringPrecise(DateTime datetime)
- {
- return datetime.ToString("yyyy-MM-dd HH:mm:ss.fff");
- }
-
- public string DumpCache()
- {
- var builder = new StringBuilder();
- if (_nameToEntry.Count > 0)
- {
- foreach (var cacheAssemblyEntry in _nameToEntry)
- {
- builder.AppendLine($"- {cacheAssemblyEntry.Value.ToString()}");
- }
- }
- else
- {
- builder.AppendLine("- [No assemblies in AssemblyLoader cache]");
- }
-
- return builder.ToString();
- }
-
- public void UpdateCache()
- {
- #if BURST_COMPILER_SHARED
- if (!CheckAssemblyDirty) return;
-
- var anythingChanged = false;
- lock (_assemblyWatcherChangedFolders)
- {
- foreach (var changedFolder in _assemblyWatcherChangedFolders)
- {
- OnLogDebug?.Invoke(LogMessageType.Debug, $"Folder changed: `{changedFolder}`");
-
- foreach (var key in _fileToEntry.Keys)
- {
- if (key.StartsWith(changedFolder, StringComparison.InvariantCultureIgnoreCase))
- {
- var entry = _fileToEntry[key];
- entry.FileTimeVerified = false;
- OnLogDebug?.Invoke(LogMessageType.Debug, $"Assembly marked dirty: `{key}`");
- anythingChanged = true;
- }
- }
- }
- _assemblyWatcherChangedFolders.Clear();
-
- foreach (var key in _fileToEntry.Keys)
- {
- var entry = _fileToEntry[key];
- if (entry.FileTimeVerified)
- {
- OnLogDebug?.Invoke(LogMessageType.Debug, $"Assembly not dirty: `{key}`");
- }
- }
- }
-
- if (!anythingChanged)
- {
- return;
- }
-
- var keys = _nameToEntry.Keys.ToArray();
-
- foreach (var key in keys)
- {
- var entry = _nameToEntry[key];
- if (IsCacheEntryDirtyAndNotify(entry))
- {
- RemoveEntryFromCache(key, entry);
- }
- }
- #endif
- }
-
- private void RemoveEntryFromCache(string entryKey, CacheAssemblyEntry entry)
- {
- #if BURST_COMPILER_SHARED
- PdbCacheReference.RemoveEntry(entry.Definition, OnLogDebug);
- #endif
- _nameToEntry.Remove(entryKey);
- _fileToEntry.Remove(entry.FilePath);
- }
-
- public new void AddSearchDirectory(string directory)
- {
- if (!GetSearchDirectories().Contains(directory))
- {
- base.AddSearchDirectory(directory);
- }
- }
-
- /// <summary>
- /// Loads the specified assembly.
- /// </summary>
- /// <param name="assemblyLocation">The assembly location.</param>
- /// <param name="useSymbols">if set to <c>true</c> load and use the pdb symbols.</param>
- /// <exception cref="ArgumentNullException">assemblyLocation</exception>
- /// <returns>The loaded Assembly definition.</returns>
- public AssemblyDefinition LoadFromFile(string assemblyLocation)
- {
- if (assemblyLocation == null) throw new ArgumentNullException(nameof(assemblyLocation));
-
- // If the file was already loaded, don't try to load it
- CacheAssemblyEntry cacheEntry;
- assemblyLocation = NormalizeFilePath(assemblyLocation);
- if (this._fileToEntry.TryGetValue(assemblyLocation, out cacheEntry))
- {
- if (!IsCacheEntryDirtyAndNotify(cacheEntry))
- {
- return cacheEntry.Definition;
- }
-
- RemoveEntryFromCache(cacheEntry.Name, cacheEntry);
- }
-
- if (assemblyLocation == null) throw new ArgumentNullException(nameof(assemblyLocation));
- var readerParams = CreateReaderParameters();
- AssemblyDefinition assemblyDefinition;
- try
- {
- assemblyDefinition = AssemblyDefinition.ReadAssembly(assemblyLocation, readerParams);
- }
- catch (Exception)
- {
- if (readerParams.ReadSymbols == true)
- {
- // Attempt to load without symbols
- readerParams.ReadSymbols = false;
- assemblyDefinition = AssemblyDefinition.ReadAssembly(assemblyLocation, readerParams);
- }
- else
- {
- throw;
- }
- }
- // AssemblyDefinition.Load For some reason, assemblyLoader doesn't cache properly
- RegisterAssembly(assemblyDefinition.Name, assemblyDefinition);
-
- return assemblyDefinition;
- }
-
- private ReaderParameters CreateReaderParameters()
- {
- var readerParams = new ReaderParameters
- {
- InMemory = true,
- AssemblyResolver = this,
- MetadataResolver = new CustomMetadataResolver(this),
- 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
- };
-
- if (LoadDebugSymbols)
- {
- readerParams.SymbolReaderProvider = new CustomSymbolReaderProvider(null);
- }
-
- return readerParams;
- }
-
- #if BURST_COMPILER_SHARED
- /// <summary>
- /// Resolves a cecil <see cref="MethodReference"/> from a <see cref="MethodReferenceString"/>
- /// </summary>
- /// <param name="methodReferenceString">A method reference string</param>
- /// <returns>A cecil <see cref="MethodReference"/></returns>
- public MethodReference Resolve(MethodReferenceString methodReferenceString)
- {
- if (methodReferenceString == null) throw new ArgumentNullException(nameof(methodReferenceString));
-
- var typeReference = Resolve(methodReferenceString.DeclaringType);
-
- var typeDefinition = typeReference.StrictResolve();
-
- // Initial capacity 1 because that is overwhelmingly the likely outcome.
- var methods = new List<MethodDefinition>(1);
-
- foreach (var m in typeDefinition.Methods)
- {
- if (m.Name != methodReferenceString.Name)
- {
- continue;
- }
-
- if (m.Parameters.Count != methodReferenceString.ParameterTypes.Count)
- {
- continue;
- }
-
- methods.Add(m);
- }
-
- // We expect to match at least one method
- if (methods.Count == 0)
- {
- throw new InvalidOperationException($"Unable to find the method `{methodReferenceString}` from type `{typeReference}`");
- }
-
- // 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.
- if (methods.Count > 1)
- {
- MethodDefinition methodToUse = null;
-
- foreach (var m in methods)
- {
- var count = m.Parameters.Count;
-
- var match = true;
-
- for (int i = 0; i < count; i++)
- {
- var parameterTypeFullName = methodReferenceString.ParameterTypes[i].ToString(ReferenceStringFormatOptions.None).Replace("+", "/").Replace("[[", "<").Replace("]]", ">");
-
- if (m.Parameters[i].ParameterType.FullName != parameterTypeFullName)
- {
- match = false;
- break;
- }
- }
-
- if (match)
- {
- methodToUse = m;
- break;
- }
- }
-
- if (methodToUse == null)
- {
- throw new Exception($"Unable to resolve the method `{methodReferenceString}` from type `{typeReference}` to a single method from set of {methods.Count} methods");
- }
-
- methods.Clear();
- methods.Add(methodToUse);
- }
-
- var method = methods[0];
- var methodReference = new MethodReference(method.Name, method.ReturnType, typeReference);
-
- foreach (var param in method.Parameters)
- {
- methodReference.Parameters.Add(new ParameterDefinition(param.Name, param.Attributes, param.ParameterType));
- }
-
- return methodReference;
- }
-
- /// <summary>
- /// Resolves a Cecil <see cref="MethodReference"/> from a reflection <see cref="MethodInfo"/>.
- /// Only used for testing purposes.
- /// </summary>
- /// <remarks>
- /// The <see cref="AssemblyDefinition"/> loaded is cached.
- /// </remarks>
- public MethodReference Resolve(System.Reflection.MethodInfo method)
- {
- if (method == null) throw new ArgumentNullException(nameof(method));
- if (method.DeclaringType == null) throw new NotSupportedException($"The method `{method}` must have a declaring type");
-
- var thisMethodAssemblyLocation = method.DeclaringType.Assembly.Location;
- var assemblyLocation = thisMethodAssemblyLocation;
- if (assemblyLocation == null)
- {
- throw new ArgumentException($"Cannot determine the assembly location for the method `{method}`", nameof(method));
- }
- if (!File.Exists(assemblyLocation))
- {
- throw new FileNotFoundException($"The assembly [{assemblyLocation}] was not found");
- }
-
- AssemblyDefinition definition;
- // Force to load a mono compiled assembly (used on Windows to cross tests between .NET CLR and Mono CLR)
- // For convenience, we add automatically the search directory for the assembly path
- // based on the method being compiled and the generic parameters
- var assemblyLocationFolder = Path.GetDirectoryName(assemblyLocation);
- AddSearchDirectory(assemblyLocationFolder);
-
- if (assemblyLocation != thisMethodAssemblyLocation)
- {
- assemblyLocationFolder = Path.GetDirectoryName(thisMethodAssemblyLocation);
- AddSearchDirectory(assemblyLocationFolder);
- }
-
- // Helper loop to extract assembly path locations from generic parameters (on declaring type and method)
- // TODO: this is not entirely correct, we would have to inspect deep nested generics to really support this
- foreach (var genericArgument in method.DeclaringType.GetGenericArguments().Concat(method.GetGenericArguments()))
- {
- var location = Path.GetDirectoryName(genericArgument.Assembly.Location);
- AddSearchDirectory(location);
- }
-
- var assemblyName = method.DeclaringType.Assembly.GetName();
- definition = Resolve(new AssemblyNameReference(assemblyName.Name, assemblyName.Version));
-
- // Resolve the Cecil MethodReference from the System.Reflection.MethodInfo
- var methodReference = definition.MainModule.ImportReference(method);
-
- // NOTE: this is a workaround for ref readonly return where Cecil is actually transforming it to an InAttribute as a modreq
- // while the C# modreq is actually `IsReadOnlyAttribute`
- // So in that case we transform the return type with the modreq expected by Cecil
- // otherwise the following methodReference.Resolve() would fail finding the method definition
- // IsReadOnlyAttribute Not Available until netstandard 2.1
- bool hasReadOnlyAttribute = false;
- foreach (var attr in method.ReturnTypeCustomAttributes.GetCustomAttributes(true))
- {
- if (attr.ToString() == "System.Runtime.CompilerServices.IsReadOnlyAttribute")
- {
- hasReadOnlyAttribute = true;
- break;
- }
- }
- if (hasReadOnlyAttribute)
- {
- var typeRef = definition.MainModule.ImportReference(typeof(System.Runtime.InteropServices.InAttribute));
- methodReference.ReturnType = new RequiredModifierType(typeRef, methodReference.ReturnType);
- }
-
- if (methodReference?.Resolve() == null)
- {
- throw new InvalidOperationException($"Unable to find method `{methodReference}` from assembly location `{assemblyLocation}`");
- }
-
- return methodReference;
- }
-
- /// <summary>
- /// Resolves a cecil <see cref="TypeReference"/> from a <see cref="SimpleTypeReferenceString"/>
- /// </summary>
- /// <param name="simpleTypeReferenceString">A type reference string</param>
- /// <returns>A cecil <see cref="TypeReference"/></returns>
- public TypeReference Resolve(SimpleTypeReferenceString simpleTypeReferenceString)
- {
- if (simpleTypeReferenceString == null) throw new ArgumentNullException(nameof(simpleTypeReferenceString));
- var assemblyOfMainType = Resolve(simpleTypeReferenceString.Assembly);
-
- var subTypes = simpleTypeReferenceString.FullName.Split(new char[] {'+'});
- Guard.Assert(subTypes.Length >= 1);
- var typeReference = (TypeReference)assemblyOfMainType.MainModule.GetType(subTypes[0]);
- if (typeReference == null)
- {
- throw new InvalidOperationException(
- $"Unable to find type `{simpleTypeReferenceString.FullName}` from assembly `{simpleTypeReferenceString.Assembly}`");
- }
-
- for (var i = 1; i < subTypes.Length; i++)
- {
- var subType = subTypes[i];
- var definition = typeReference.StrictResolve();
- var nestedDefinition = definition.NestedTypes.FirstOrDefault(nestedType => nestedType.Name == subType);
- if (nestedDefinition == null)
- {
- throw new InvalidOperationException($"Unable to find nested type `{subType}` from typename `{simpleTypeReferenceString.FullName}` assembly `{simpleTypeReferenceString.Assembly}`");
- }
- typeReference = nestedDefinition;
- }
-
- var generic = simpleTypeReferenceString as GenericInstanceTypeReferenceString;
- if (generic != null)
- {
- var genericType = new GenericInstanceType(typeReference);
- foreach (var genericArgType in generic.GenericArguments)
- {
- var simpleGenericArgType = genericArgType as SimpleTypeReferenceString;
- if (simpleGenericArgType == null)
- {
- throw new InvalidOperationException($"Unable to resolve generic argument `{genericArgType}`");
- }
- genericType.GenericArguments.Add(Resolve(simpleGenericArgType));
- }
- typeReference = genericType;
- }
-
- return typeReference;
- }
- #endif
-
- private void RegisterAssembly(AssemblyNameReference name, AssemblyDefinition assembly)
- {
- if (assembly == null)
- throw new ArgumentNullException(nameof(assembly));
- string fullName = name.FullName;
- var filename = GetAssemblyFileName(assembly);
- var entry = new CacheAssemblyEntry(assembly, filename);
- _nameToEntry[fullName] = entry;
- // Duplicate the entry (mscorlib 2.0.0 can be remapped to 4.0.0, so better cache them both)
- _nameToEntry[assembly.Name.FullName] = entry;
- _fileToEntry[filename] = entry;
- #if BURST_COMPILER_SHARED
- PdbCacheReference.AddEntry(assembly, OnLogDebug);
- #endif
- }
-
- private string GetAssemblyFileName(AssemblyDefinition assembly)
- {
- string fileName = assembly.MainModule.FileName;
- if (fileName == null)
- {
- throw new InvalidOperationException($"Unable to find original assembly file from {assembly.Name}");
- }
- return NormalizeFilePath(fileName);
- }
-
- protected override void Dispose(bool disposing)
- {
- #if BURST_COMPILER_SHARED
- if (_assemblyWatcherManager != null)
- {
- _assemblyWatcherManager.OnFolderChanged -= OnAssemblyWatcherFolderChanged;
- _assemblyWatcherManager.Dispose();
- }
- #endif
-
- Clear();
- base.Dispose(disposing);
- }
-
- private class CacheAssemblyEntry
- {
- public CacheAssemblyEntry(AssemblyDefinition assemblyDefinition, string filePath)
- {
- Definition = assemblyDefinition;
- FilePath = filePath;
- FileTime = File.GetLastWriteTime(filePath);
- FileTimeVerified = true;
- }
-
- public string Name => Definition.FullName;
-
- public readonly AssemblyDefinition Definition;
-
- public readonly string FilePath;
-
- public readonly DateTime FileTime;
-
- public bool FileTimeVerified { get; set; }
-
- public override string ToString() => $"{Name} => {FilePath} {DateTimeToStringPrecise(FileTime)}";
- }
-
- private static string NormalizeFilePath(string path)
- {
- return Path.GetFullPath(new Uri(path).LocalPath).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
- }
-
- private class CustomMetadataResolver : MetadataResolver
- {
- public CustomMetadataResolver(IAssemblyResolver assemblyResolver) : base(assemblyResolver)
- {
- }
-
- public override MethodDefinition Resolve(MethodReference method)
- {
- if (method is MethodDefinition methodDef)
- {
- return methodDef;
- }
-
- if (method.GetElementMethod() is MethodDefinition methodDef2)
- {
- return methodDef2;
- }
-
- return base.Resolve(method);
- }
- }
-
- /// <summary>
- /// Custom implementation of <see cref="ISymbolReaderProvider"/> to:
- /// - to load pdb/mdb through a MemoryStream to avoid locking the file on the disk
- /// - catch any exceptions while loading the symbols and report them back
- /// </summary>
- private class CustomSymbolReaderProvider : ISymbolReaderProvider
- {
- private readonly Action<string, Exception> _logException;
-
- public CustomSymbolReaderProvider(Action<string, Exception> logException)
- {
- _logException = logException;
- }
-
- public ISymbolReader GetSymbolReader(ModuleDefinition module, string fileName)
- {
- if (string.IsNullOrWhiteSpace(fileName)) return null;
-
- string pdbFileName = fileName;
- try
- {
- fileName = NormalizeFilePath(fileName);
- pdbFileName = GetPdbFileName(fileName);
-
- if (File.Exists(pdbFileName))
- {
- var pdbStream = ReadToMemoryStream(pdbFileName);
- if (IsPortablePdb(pdbStream))
- return new SafeDebugReaderProvider(new PortablePdbReaderProvider().GetSymbolReader(module, pdbStream));
-
- return new SafeDebugReaderProvider(new NativePdbReaderProvider().GetSymbolReader(module, pdbStream));
- }
- }
- catch (Exception ex) when (_logException != null)
- {
- _logException?.Invoke($"Unable to load symbol `{pdbFileName}`", ex);
- return null;
- }
- return null;
- }
-
- private static MemoryStream ReadToMemoryStream(string filename)
- {
- return new MemoryStream(File.ReadAllBytes(filename));
- }
-
- public ISymbolReader GetSymbolReader(ModuleDefinition module, Stream symbolStream)
- {
- throw new NotSupportedException();
- }
-
- private static string GetPdbFileName(string assemblyFileName)
- {
- return Path.ChangeExtension(assemblyFileName, ".pdb");
- }
-
- private static bool IsPortablePdb(Stream stream)
- {
- if (stream.Length < 4L)
- return false;
- long position = stream.Position;
- try
- {
- return (int)new BinaryReader(stream).ReadUInt32() == 1112167234;
- }
- finally
- {
- stream.Position = position;
- }
- }
-
- /// <summary>
- /// This class is a wrapper around <see cref="ISymbolReader"/> to protect
- /// against failure while trying to read debug information in Mono.Cecil
- /// </summary>
- private class SafeDebugReaderProvider : ISymbolReader
- {
- private readonly ISymbolReader _reader;
-
- public SafeDebugReaderProvider(ISymbolReader reader)
- {
- _reader = reader;
- }
-
-
- public void Dispose()
- {
- try
- {
- _reader.Dispose();
- }
- catch
- {
- // ignored
- }
- }
-
- public ISymbolWriterProvider GetWriterProvider()
- {
- // We are not protecting here as we are not suppose to write to PDBs
- return _reader.GetWriterProvider();
- }
-
- public bool ProcessDebugHeader(ImageDebugHeader header)
- {
- try
- {
- return _reader.ProcessDebugHeader(header);
- }
- catch
- {
- // ignored
- }
-
- return false;
- }
-
- public MethodDebugInformation Read(MethodDefinition method)
- {
- try
- {
- return _reader.Read(method);
- }
- catch
- {
- // ignored
- }
- return null;
- }
- }
- }
-
- #if !BURST_COMPILER_SHARED
- public enum LogMessageType
- {
- Debug,
- }
- #endif
- }
-
- /// <summary>
- /// This class is a container for keeping an assembly reference and path together
- /// </summary>
- internal sealed class AssemblyNameReferenceAndPath
- {
- /// <summary>
- /// Get the assembly name reference
- /// </summary>
- public readonly AssemblyNameReference AssemblyNameReference;
- /// <summary>
- /// Get the full path to the assembly
- /// </summary>
- public readonly string FullPath;
-
- internal AssemblyNameReferenceAndPath(AssemblyNameReference assemblyNameReference, string fullPath)
- {
- AssemblyNameReference = assemblyNameReference;
- FullPath = fullPath;
- }
- }
- }
|