From b01ad3e6b61ad48640a90d8dbabbcc1dbb14b578 Mon Sep 17 00:00:00 2001 From: svg2003 Date: Wed, 5 Apr 2023 20:48:11 -0700 Subject: [PATCH 1/2] Use custom context only (don't use assemblies, loaded into AppDomain). Try to load assembly from trusted list, before to start using some complex logic and enumerate AdditinalFrameworkDirectories Improve logging for assembly resolving process --- .../Internal/TestAssemblyLoadContext.cs | 37 ++++++++-- .../Internal/TestAssemblyResolver.cs | 73 ++++++++++++++++++- 2 files changed, 101 insertions(+), 9 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs index 69d432fb4..9be338baf 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs @@ -12,6 +12,8 @@ namespace NUnit.Engine.Internal { internal sealed class TestAssemblyLoadContext : AssemblyLoadContext { + private static readonly Logger _log = InternalTrace.GetLogger(typeof(TestAssemblyLoadContext)); + private readonly string _testAssemblyPath; private readonly string _basePath; private readonly TestAssemblyResolver _resolver; @@ -27,19 +29,16 @@ public TestAssemblyLoadContext(string testAssemblyPath) protected override Assembly Load(AssemblyName name) { - var assemblies = AppDomain.CurrentDomain.GetAssemblies(); - var loadedAssembly = assemblies.FirstOrDefault(x => x.GetName().Name == name.Name); - if (loadedAssembly != null) - { - return loadedAssembly; - } + _log.Debug("Loading {0} assembly", name); - loadedAssembly = base.Load(name); + var loadedAssembly = base.Load(name); if (loadedAssembly != null) { + _log.Info("Assembly {0} ({1}) is loaded using default base.Load()", name, GetAssemblyLocationInfo(loadedAssembly)); return loadedAssembly; } + var runtimeResolverPath = _runtimeResolver.ResolveAssemblyToPath(name); if (string.IsNullOrEmpty(runtimeResolverPath) == false && File.Exists(runtimeResolverPath)) @@ -49,12 +48,15 @@ protected override Assembly Load(AssemblyName name) if (loadedAssembly != null) { + _log.Info("Assembly {0} ({1}) is loaded using the deps.json info", name, GetAssemblyLocationInfo(loadedAssembly)); return loadedAssembly; } loadedAssembly = _resolver.Resolve(this, name); if (loadedAssembly != null) { + _log.Info("Assembly {0} ({1}) is loaded using the TestAssembliesResolver", name, GetAssemblyLocationInfo(loadedAssembly)); + return loadedAssembly; } @@ -68,8 +70,29 @@ protected override Assembly Load(AssemblyName name) loadedAssembly = LoadFromAssemblyPath(assemblyPath); } + if (loadedAssembly != null) + { + _log.Info("Assembly {0} ({1}) is loaded using base path", name, GetAssemblyLocationInfo(loadedAssembly)); + return loadedAssembly; + } + return loadedAssembly; } + + private static string GetAssemblyLocationInfo(Assembly assembly) + { + if (assembly.IsDynamic) + { + return $"Dynamic {assembly.FullName}"; + } + + if (string.IsNullOrEmpty(assembly.Location)) + { + return $"No location for {assembly.FullName}"; + } + + return $"{assembly.FullName} from {assembly.Location}"; + } } } diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs index c5c28f4d7..a9c48dedb 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs @@ -17,6 +17,8 @@ namespace NUnit.Engine.Internal { internal sealed class TestAssemblyResolver : IDisposable { + private static readonly Logger _log = InternalTrace.GetLogger(typeof(TestAssemblyResolver)); + private readonly ICompilationAssemblyResolver _assemblyResolver; private readonly DependencyContext _dependencyContext; private readonly AssemblyLoadContext _loadContext; @@ -62,6 +64,14 @@ public Assembly Resolve(AssemblyLoadContext context, AssemblyName name) private Assembly OnResolving(AssemblyLoadContext context, AssemblyName name) { + context = context ?? _loadContext; + + if (TryLoadFromTrustedPlatformAssemblies(context, name, out var loadedAssembly)) + { + _log.Info("'{0}' assembly is loaded from trusted path '{1}'", name, loadedAssembly.Location); + return loadedAssembly; + } + foreach (var library in _dependencyContext.RuntimeLibraries) { var wrapper = new CompilationLibrary( @@ -79,24 +89,81 @@ private Assembly OnResolving(AssemblyLoadContext context, AssemblyName name) foreach (var assemblyPath in assemblies) { if (name.Name == Path.GetFileNameWithoutExtension(assemblyPath)) - return _loadContext.LoadFromAssemblyPath(assemblyPath); + { + loadedAssembly = context.LoadFromAssemblyPath(assemblyPath); + _log.Info("'{0}' ({1}) assembly is loaded from runtime libraries {2} dependencies", + name, + loadedAssembly.Location, + library.Name); + + return loadedAssembly; + } } } + if (name.Version == null) + { + return null; + } + foreach (string frameworkDirectory in AdditionalFrameworkDirectories) { var versionDir = FindBestVersionDir(frameworkDirectory, name.Version); + if (versionDir != null) { string candidate = Path.Combine(frameworkDirectory, versionDir, name.Name + ".dll"); if (File.Exists(candidate)) - return _loadContext.LoadFromAssemblyPath(candidate); + { + loadedAssembly = context.LoadFromAssemblyPath(candidate); + _log.Info("'{0}' ({1}) assembly is loaded from AdditionalFrameworkDirectory {2} dependencies with best candidate version {3}", + name, + loadedAssembly.Location, + frameworkDirectory, + versionDir); + + return loadedAssembly; + } + else + { + _log.Debug("Best version dir for {0} is {1}, but there is no {2} file", frameworkDirectory, versionDir, candidate); + } } } + _log.Info("Cannot resolve assembly '{0}'", name); return null; } + private static bool TryLoadFromTrustedPlatformAssemblies(AssemblyLoadContext context, AssemblyName assemblyName, out Assembly loadedAssembly) + { + // https://learn.microsoft.com/en-us/dotnet/core/dependency-loading/default-probing + loadedAssembly = null; + var trustedAssemblies = System.AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") as string; + if (string.IsNullOrEmpty(trustedAssemblies)) + { + return false; + } + + var separator = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ";" : ":"; + foreach (var assemblyPath in trustedAssemblies.Split(separator)) + { + var fileName = Path.GetFileNameWithoutExtension(assemblyPath); + if (string.Equals(fileName, assemblyName.Name, StringComparison.InvariantCultureIgnoreCase) == false) + { + continue; + } + + if (File.Exists(assemblyPath)) + { + loadedAssembly = context.LoadFromAssemblyPath(assemblyPath); + return true; + } + } + + return false; + } + private static string GetDotNetInstallDirectory() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -119,9 +186,11 @@ private static string FindBestVersionDir(string libraryDir, Version targetVersio { Version version; if (TryGetVersionFromString(Path.GetFileName(subdir), out version)) + { if (version >= targetVersion) if (bestVersion.Major == 0 || bestVersion > version) bestVersion = version; + } } return bestVersion.Major > 0 From 38d141eaa8cf71c1095aeb5050b9a5641c71452d Mon Sep 17 00:00:00 2001 From: svg2003 Date: Sat, 15 Apr 2023 16:17:41 -0700 Subject: [PATCH 2/2] renamed "_log" variable into "log" (cherry picked from commit f642e99e5e77e014d0854e69df1a78903906d94a) --- .../Internal/TestAssemblyLoadContext.cs | 12 ++++++------ .../Internal/TestAssemblyResolver.cs | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs index 9be338baf..cf9ed03f9 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs @@ -12,7 +12,7 @@ namespace NUnit.Engine.Internal { internal sealed class TestAssemblyLoadContext : AssemblyLoadContext { - private static readonly Logger _log = InternalTrace.GetLogger(typeof(TestAssemblyLoadContext)); + private static readonly Logger log = InternalTrace.GetLogger(typeof(TestAssemblyLoadContext)); private readonly string _testAssemblyPath; private readonly string _basePath; @@ -29,12 +29,12 @@ public TestAssemblyLoadContext(string testAssemblyPath) protected override Assembly Load(AssemblyName name) { - _log.Debug("Loading {0} assembly", name); + log.Debug("Loading {0} assembly", name); var loadedAssembly = base.Load(name); if (loadedAssembly != null) { - _log.Info("Assembly {0} ({1}) is loaded using default base.Load()", name, GetAssemblyLocationInfo(loadedAssembly)); + log.Info("Assembly {0} ({1}) is loaded using default base.Load()", name, GetAssemblyLocationInfo(loadedAssembly)); return loadedAssembly; } @@ -48,14 +48,14 @@ protected override Assembly Load(AssemblyName name) if (loadedAssembly != null) { - _log.Info("Assembly {0} ({1}) is loaded using the deps.json info", name, GetAssemblyLocationInfo(loadedAssembly)); + log.Info("Assembly {0} ({1}) is loaded using the deps.json info", name, GetAssemblyLocationInfo(loadedAssembly)); return loadedAssembly; } loadedAssembly = _resolver.Resolve(this, name); if (loadedAssembly != null) { - _log.Info("Assembly {0} ({1}) is loaded using the TestAssembliesResolver", name, GetAssemblyLocationInfo(loadedAssembly)); + log.Info("Assembly {0} ({1}) is loaded using the TestAssembliesResolver", name, GetAssemblyLocationInfo(loadedAssembly)); return loadedAssembly; } @@ -72,7 +72,7 @@ protected override Assembly Load(AssemblyName name) if (loadedAssembly != null) { - _log.Info("Assembly {0} ({1}) is loaded using base path", name, GetAssemblyLocationInfo(loadedAssembly)); + log.Info("Assembly {0} ({1}) is loaded using base path", name, GetAssemblyLocationInfo(loadedAssembly)); return loadedAssembly; } diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs index a9c48dedb..c3cfc570a 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs @@ -17,7 +17,7 @@ namespace NUnit.Engine.Internal { internal sealed class TestAssemblyResolver : IDisposable { - private static readonly Logger _log = InternalTrace.GetLogger(typeof(TestAssemblyResolver)); + private static readonly Logger log = InternalTrace.GetLogger(typeof(TestAssemblyResolver)); private readonly ICompilationAssemblyResolver _assemblyResolver; private readonly DependencyContext _dependencyContext; @@ -68,7 +68,7 @@ private Assembly OnResolving(AssemblyLoadContext context, AssemblyName name) if (TryLoadFromTrustedPlatformAssemblies(context, name, out var loadedAssembly)) { - _log.Info("'{0}' assembly is loaded from trusted path '{1}'", name, loadedAssembly.Location); + log.Info("'{0}' assembly is loaded from trusted path '{1}'", name, loadedAssembly.Location); return loadedAssembly; } @@ -91,7 +91,7 @@ private Assembly OnResolving(AssemblyLoadContext context, AssemblyName name) if (name.Name == Path.GetFileNameWithoutExtension(assemblyPath)) { loadedAssembly = context.LoadFromAssemblyPath(assemblyPath); - _log.Info("'{0}' ({1}) assembly is loaded from runtime libraries {2} dependencies", + log.Info("'{0}' ({1}) assembly is loaded from runtime libraries {2} dependencies", name, loadedAssembly.Location, library.Name); @@ -116,7 +116,7 @@ private Assembly OnResolving(AssemblyLoadContext context, AssemblyName name) if (File.Exists(candidate)) { loadedAssembly = context.LoadFromAssemblyPath(candidate); - _log.Info("'{0}' ({1}) assembly is loaded from AdditionalFrameworkDirectory {2} dependencies with best candidate version {3}", + log.Info("'{0}' ({1}) assembly is loaded from AdditionalFrameworkDirectory {2} dependencies with best candidate version {3}", name, loadedAssembly.Location, frameworkDirectory, @@ -126,12 +126,12 @@ private Assembly OnResolving(AssemblyLoadContext context, AssemblyName name) } else { - _log.Debug("Best version dir for {0} is {1}, but there is no {2} file", frameworkDirectory, versionDir, candidate); + log.Debug("Best version dir for {0} is {1}, but there is no {2} file", frameworkDirectory, versionDir, candidate); } } } - _log.Info("Cannot resolve assembly '{0}'", name); + log.Info("Cannot resolve assembly '{0}'", name); return null; }