From 342a6bbcf90400fb1df2cc7060c18b3ea691f18b Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Thu, 26 Oct 2023 10:07:01 -0400 Subject: [PATCH] - Added try-catch to assembly compilation call. - Added empty list checks for metadata references building. - Made ACL cleanup more reliable. - Updated some GET functions in AssemblyManager to have better reliability and error handling. --- .../LuaCs/Plugins/AssemblyManager.cs | 46 ++++++++++++--- .../MemoryFileAssemblyContextLoader.cs | 56 ++++++++++--------- 2 files changed, 69 insertions(+), 33 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/AssemblyManager.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/AssemblyManager.cs index ea5e57b865..bb0e33a600 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/AssemblyManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/AssemblyManager.cs @@ -260,7 +260,8 @@ public IEnumerable GetTypesByName(string typeName) } catch (Exception e) { - this.OnException?.Invoke($"{nameof(AssemblyManager)}::{nameof(GetTypesByName)}() | Error: {e.Message}", e); + this.OnException?.Invoke( + $"{nameof(AssemblyManager)}::{nameof(GetTypesByName)}() | Error: {e.Message}", e); } } } @@ -309,12 +310,16 @@ public IEnumerable GetAllTypesInLoadedAssemblies() OpsLockLoaded.EnterReadLock(); try { - return AssemblyLoadContext.Default.Assemblies - .SelectMany(a => a.GetSafeTypes()) + return _defaultContextTypes + .Select(kvp => kvp.Value) .Concat(LoadedACLs - .SelectMany(kvp => kvp.Value.AssembliesTypes.Select(kv => kv.Value))) + .SelectMany(kvp => kvp.Value?.AssembliesTypes.Select(kv => kv.Value))) .ToImmutableList(); } + catch + { + return ImmutableList.Empty; + } finally { OpsLockLoaded.ExitReadLock(); @@ -332,8 +337,17 @@ public IEnumerable GetAllLoadedACLs() OpsLockLoaded.EnterReadLock(); try { + if (LoadedACLs.IsEmpty) + { + return ImmutableList.Empty; + } + return LoadedACLs.Select(kvp => kvp.Value).ToImmutableList(); } + catch + { + return ImmutableList.Empty; + } finally { OpsLockLoaded.ExitReadLock(); @@ -387,8 +401,18 @@ public AssemblyLoadingSuccessState LoadAssemblyFromMemory([NotNull] string compi return AssemblyLoadingSuccessState.AlreadyLoaded; // compile - var state = acl.Acl.CompileAndLoadScriptAssembly(compiledAssemblyName, syntaxTree, externalMetadataReferences, - compilationOptions, out var messages, externFileAssemblyRefs); + AssemblyLoadingSuccessState state; + string messages; + try + { + state = acl.Acl.CompileAndLoadScriptAssembly(compiledAssemblyName, syntaxTree, externalMetadataReferences, + compilationOptions, out messages, externFileAssemblyRefs); + } + catch (Exception e) + { + ModUtils.Logging.PrintError($"{nameof(AssemblyManager)}::{nameof(LoadAssemblyFromMemory)}() | Failed to compile and load assemblies for [ {compiledAssemblyName} / {friendlyName} ]! Details: {e.Message}"); + return AssemblyLoadingSuccessState.InvalidAssembly; + } // get types if (state is AssemblyLoadingSuccessState.Success) @@ -654,7 +678,15 @@ private bool DisposeACL(Guid id) OpsLockUnloaded.EnterWriteLock(); try { - if (id.Equals(Guid.Empty) || !LoadedACLs.ContainsKey(id) || LoadedACLs[id] is null) + if (LoadedACLs.ContainsKey(id) && LoadedACLs[id] == null) + { + if (!LoadedACLs.TryRemove(id, out _)) + { + ModUtils.Logging.PrintWarning($"An ACL with the GUID {id.ToString()} was found as null. Unable to remove null ACL entry."); + } + } + + if (id.Equals(Guid.Empty) || !LoadedACLs.ContainsKey(id)) { return false; // nothing to dispose of } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/MemoryFileAssemblyContextLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/MemoryFileAssemblyContextLoader.cs index 66d3bd0040..94e3edd748 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/MemoryFileAssemblyContextLoader.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/MemoryFileAssemblyContextLoader.cs @@ -143,7 +143,7 @@ public AssemblyLoadingSuccessState CompileAndLoadScriptAssembly( } var externAssemblyRefs = externFileAssemblyReferences is not null ? externFileAssemblyReferences.ToImmutableList() : ImmutableList.Empty; - var externAssemblyNames = externAssemblyRefs.Any() ? externAssemblyRefs + var externAssemblyNames = !externAssemblyRefs.IsEmpty ? externAssemblyRefs .Where(a => a.FullName is not null) .Select(a => a.FullName).ToImmutableHashSet() : ImmutableHashSet.Empty; @@ -178,35 +178,39 @@ public AssemblyLoadingSuccessState CompileAndLoadScriptAssembly( .Select(a => MetadataReference.CreateFromFile(a.Location) as MetadataReference) ).ToList()); - // build metadata refs from ACL assemblies from files/disk. - foreach (AssemblyManager.LoadedACL loadedAcl in _assemblyManager.GetAllLoadedACLs()) + ImmutableList loadedAcls = _assemblyManager.GetAllLoadedACLs().ToImmutableList(); + if (!loadedAcls.IsEmpty) { - if(loadedAcl.Acl.IsTemplateMode || loadedAcl.Acl.IsDisposed) - continue; - metadataReferences.AddRange(loadedAcl.Acl.Assemblies - .Where(a => - { - if (a.IsDynamic || string.IsNullOrWhiteSpace(a.Location) || a.Location.Contains("xunit")) - return false; - if (a.FullName is null) - return true; - return !externAssemblyNames.Contains(a.FullName); // exclude duplicates - }) - .Select(a => MetadataReference.CreateFromFile(a.Location) as MetadataReference) - .Union(externAssemblyRefs // add custom supplied assemblies - .Where(a => !(a.IsDynamic || string.IsNullOrEmpty(a.Location) || a.Location.Contains("xunit"))) + // build metadata refs from ACL assemblies from files/disk. + foreach (AssemblyManager.LoadedACL loadedAcl in loadedAcls) + { + if(loadedAcl?.Acl is null || loadedAcl.Acl.IsTemplateMode || loadedAcl.Acl.IsDisposed) + continue; + metadataReferences.AddRange(loadedAcl.Acl.Assemblies + .Where(a => + { + if (a.IsDynamic || string.IsNullOrWhiteSpace(a.Location) || a.Location.Contains("xunit")) + return false; + if (a.FullName is null) + return true; + return !externAssemblyNames.Contains(a.FullName); // exclude duplicates + }) .Select(a => MetadataReference.CreateFromFile(a.Location) as MetadataReference) - ).ToList()); - } + .Union(externAssemblyRefs // add custom supplied assemblies + .Where(a => !(a.IsDynamic || string.IsNullOrEmpty(a.Location) || a.Location.Contains("xunit"))) + .Select(a => MetadataReference.CreateFromFile(a.Location) as MetadataReference) + ).ToList()); + } - // build metadata refs from in-memory images - foreach (var loadedAcl in _assemblyManager.GetAllLoadedACLs()) - { - if (loadedAcl.Acl.CompiledAssemblyImage is null || loadedAcl.Acl.CompiledAssemblyImage.Length == 0) - continue; - metadataReferences.Add(MetadataReference.CreateFromImage(loadedAcl.Acl.CompiledAssemblyImage)); + // build metadata refs from in-memory images + foreach (var loadedAcl in loadedAcls) + { + if (loadedAcl.Acl.CompiledAssemblyImage is null || loadedAcl.Acl.CompiledAssemblyImage.Length == 0) + continue; + metadataReferences.Add(MetadataReference.CreateFromImage(loadedAcl.Acl.CompiledAssemblyImage)); + } } - + // Change inaccessible options to allow public access to restricted members var topLevelBinderFlagsProperty = typeof(CSharpCompilationOptions).GetProperty("TopLevelBinderFlags", BindingFlags.Instance | BindingFlags.NonPublic); topLevelBinderFlagsProperty?.SetValue(compilationOptions, (uint)1 << 22);