diff --git a/src/AsmResolver.DotNet/AssemblyDefinition.cs b/src/AsmResolver.DotNet/AssemblyDefinition.cs
index 814fe9f9e..942da8039 100644
--- a/src/AsmResolver.DotNet/AssemblyDefinition.cs
+++ b/src/AsmResolver.DotNet/AssemblyDefinition.cs
@@ -33,8 +33,17 @@ public class AssemblyDefinition : AssemblyDescriptor, IModuleProvider, IHasSecur
/// The raw contents of the executable file to load.
/// The module.
/// Occurs when the image does not contain a valid .NET metadata directory.
- public static AssemblyDefinition FromBytes(byte[] buffer) =>
- FromImage(PEImage.FromBytes(buffer));
+ public static AssemblyDefinition FromBytes(byte[] buffer) => FromBytes(buffer, new ModuleReaderParameters());
+
+ ///
+ /// Reads a .NET assembly from the provided input buffer.
+ ///
+ /// The raw contents of the executable file to load.
+ /// The parameters to use while reading the assembly.
+ /// The module.
+ /// Occurs when the image does not contain a valid .NET metadata directory.
+ public static AssemblyDefinition FromBytes(byte[] buffer, ModuleReaderParameters readerParameters) =>
+ FromImage(PEImage.FromBytes(buffer, readerParameters.PEReaderParameters), readerParameters);
///
/// Reads a .NET assembly from the provided input file.
@@ -42,8 +51,17 @@ public static AssemblyDefinition FromBytes(byte[] buffer) =>
/// The file path to the input executable to load.
/// The module.
/// Occurs when the image does not contain a valid .NET metadata directory.
- public static AssemblyDefinition FromFile(string filePath) =>
- FromImage(PEImage.FromFile(filePath), new ModuleReaderParameters(Path.GetDirectoryName(filePath)));
+ public static AssemblyDefinition FromFile(string filePath) => FromFile(filePath, new ModuleReaderParameters(Path.GetDirectoryName(filePath)));
+
+ ///
+ /// Reads a .NET assembly from the provided input file.
+ ///
+ /// The file path to the input executable to load.
+ /// The parameters to use while reading the assembly.
+ /// The module.
+ /// Occurs when the image does not contain a valid .NET metadata directory.
+ public static AssemblyDefinition FromFile(string filePath, ModuleReaderParameters readerParameters) =>
+ FromImage(PEImage.FromFile(filePath, readerParameters.PEReaderParameters), readerParameters);
///
/// Reads a .NET assembly from the provided input file.
@@ -51,15 +69,36 @@ public static AssemblyDefinition FromFile(string filePath) =>
/// The portable executable file to load.
/// The module.
/// Occurs when the image does not contain a valid .NET metadata directory.
- public static AssemblyDefinition FromFile(PEFile file) => FromImage(PEImage.FromFile(file));
+ public static AssemblyDefinition FromFile(PEFile file) => FromFile(file, new ModuleReaderParameters());
///
/// Reads a .NET assembly from the provided input file.
///
/// The portable executable file to load.
+ /// The parameters to use while reading the assembly.
+ /// The module.
+ /// Occurs when the image does not contain a valid .NET metadata directory.
+ public static AssemblyDefinition FromFile(PEFile file, ModuleReaderParameters readerParameters) =>
+ FromImage(PEImage.FromFile(file, readerParameters.PEReaderParameters), readerParameters);
+
+ ///
+ /// Reads a .NET assembly from the provided input file.
+ ///
+ /// The portable executable file to load.
+ /// The module.
+ /// Occurs when the image does not contain a valid .NET metadata directory.
+ public static AssemblyDefinition FromFile(IInputFile file) =>
+ FromFile(file, new ModuleReaderParameters(Path.GetDirectoryName(file.FilePath)));
+
+ ///
+ /// Reads a .NET assembly from the provided input file.
+ ///
+ /// The portable executable file to load.
+ /// The parameters to use while reading the assembly.
/// The module.
/// Occurs when the image does not contain a valid .NET metadata directory.
- public static AssemblyDefinition FromFile(IInputFile file) => FromImage(PEImage.FromFile(file));
+ public static AssemblyDefinition FromFile(IInputFile file, ModuleReaderParameters readerParameters) =>
+ FromImage(PEImage.FromFile(file, readerParameters.PEReaderParameters), readerParameters);
///
/// Reads a .NET assembly from an input stream.
diff --git a/src/AsmResolver.DotNet/AssemblyResolverBase.cs b/src/AsmResolver.DotNet/AssemblyResolverBase.cs
index 00c0f9ce9..bc48ed75d 100644
--- a/src/AsmResolver.DotNet/AssemblyResolverBase.cs
+++ b/src/AsmResolver.DotNet/AssemblyResolverBase.cs
@@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
+using AsmResolver.DotNet.Serialized;
using AsmResolver.DotNet.Signatures;
using AsmResolver.IO;
@@ -16,7 +17,7 @@ public abstract class AssemblyResolverBase : IAssemblyResolver
private static readonly string[] BinaryFileExtensions = {".dll", ".exe"};
private static readonly SignatureComparer Comparer = new(SignatureComparisonFlags.AcceptNewerVersions);
- private readonly ConcurrentDictionary _cache = new(new SignatureComparer());
+ private readonly ConcurrentDictionary _cache = new(SignatureComparer.Default);
///
/// Initializes the base of an assembly resolver.
@@ -24,13 +25,27 @@ public abstract class AssemblyResolverBase : IAssemblyResolver
/// The service to use for reading files from the disk.
protected AssemblyResolverBase(IFileService fileService)
{
- FileService = fileService ?? throw new ArgumentNullException(nameof(fileService));
+ ReaderParameters = new ModuleReaderParameters(fileService);
+ }
+
+ ///
+ /// Initializes the base of an assembly resolver.
+ ///
+ /// The reader parameters used for reading new resolved assemblies.
+ protected AssemblyResolverBase(ModuleReaderParameters readerParameters)
+ {
+ ReaderParameters = readerParameters;
}
///
/// Gets the file service that is used for reading files from the disk.
///
- public IFileService FileService
+ public IFileService FileService => ReaderParameters.PEReaderParameters.FileService;
+
+ ///
+ /// Gets the reader parameters used for reading new resolved assemblies.
+ ///
+ public ModuleReaderParameters ReaderParameters
{
get;
}
@@ -128,7 +143,7 @@ public void AddToCache(AssemblyDescriptor descriptor, AssemblyDefinition definit
/// The assembly.
protected virtual AssemblyDefinition LoadAssemblyFromFile(string path)
{
- return AssemblyDefinition.FromFile(FileService.OpenFile(path));
+ return AssemblyDefinition.FromFile(FileService.OpenFile(path), ReaderParameters);
}
///
diff --git a/src/AsmResolver.DotNet/Bundles/BundleAssemblyResolver.cs b/src/AsmResolver.DotNet/Bundles/BundleAssemblyResolver.cs
new file mode 100644
index 000000000..7d58c167d
--- /dev/null
+++ b/src/AsmResolver.DotNet/Bundles/BundleAssemblyResolver.cs
@@ -0,0 +1,90 @@
+using System.Collections.Concurrent;
+using System.IO;
+using AsmResolver.DotNet.Serialized;
+using AsmResolver.DotNet.Signatures;
+
+namespace AsmResolver.DotNet.Bundles;
+
+///
+/// Provides an implementation of an assembly resolver that prefers assemblies embedded in single-file-host executable.
+///
+public class BundleAssemblyResolver : IAssemblyResolver
+{
+ private readonly BundleManifest _manifest;
+ private readonly DotNetCoreAssemblyResolver _baseResolver;
+ private readonly ConcurrentDictionary _embeddedFilesCache = new(SignatureComparer.Default);
+
+ internal BundleAssemblyResolver(BundleManifest manifest, ModuleReaderParameters readerParameters)
+ {
+ _manifest = manifest;
+
+ // Bundles are .NET core 3.1+ only -> we can always default to .NET Core assembly resolution.
+ _baseResolver = new DotNetCoreAssemblyResolver(readerParameters, manifest.GetTargetRuntime().Version);
+ }
+
+ ///
+ public AssemblyDefinition? Resolve(AssemblyDescriptor assembly)
+ {
+ // Prefer embedded files before we forward to the default assembly resolution algorithm.
+ if (TryResolveFromEmbeddedFiles(assembly, out var resolved))
+ return resolved;
+
+ return _baseResolver.Resolve(assembly);
+ }
+
+ private bool TryResolveFromEmbeddedFiles(AssemblyDescriptor assembly, out AssemblyDefinition? resolved)
+ {
+ if (_embeddedFilesCache.TryGetValue(assembly, out resolved))
+ return true;
+
+ try
+ {
+ for (int i = 0; i < _manifest.Files.Count; i++)
+ {
+ var file = _manifest.Files[i];
+ if (file.Type != BundleFileType.Assembly)
+ continue;
+
+ if (Path.GetFileNameWithoutExtension(file.RelativePath) == assembly.Name)
+ {
+ resolved = AssemblyDefinition.FromBytes(file.GetData(), _baseResolver.ReaderParameters);
+ _embeddedFilesCache.TryAdd(assembly, resolved);
+ return true;
+ }
+ }
+ }
+ catch
+ {
+ // Ignore any reader errors.
+ }
+
+ resolved = null;
+ return false;
+ }
+
+ ///
+ public void AddToCache(AssemblyDescriptor descriptor, AssemblyDefinition definition)
+ {
+ _baseResolver.AddToCache(descriptor, definition);
+ }
+
+ ///
+ public bool RemoveFromCache(AssemblyDescriptor descriptor)
+ {
+ // Note: This is intentionally not an or-else (||) construction.
+ return _embeddedFilesCache.TryRemove(descriptor, out _) | _baseResolver.RemoveFromCache(descriptor);
+ }
+
+ ///
+ public bool HasCached(AssemblyDescriptor descriptor)
+ {
+ return _embeddedFilesCache.ContainsKey(descriptor) || _baseResolver.HasCached(descriptor);
+ }
+
+ ///
+ public void ClearCache()
+ {
+ _embeddedFilesCache.Clear();
+ _baseResolver.ClearCache();
+ }
+}
diff --git a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs
index 58e82defb..0885f3d82 100644
--- a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs
+++ b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs
@@ -6,10 +6,10 @@
using System.Text;
using System.Threading;
using AsmResolver.Collections;
+using AsmResolver.DotNet.Config.Json;
using AsmResolver.IO;
using AsmResolver.PE.File;
using AsmResolver.PE.File.Headers;
-using AsmResolver.PE.Win32Resources;
using AsmResolver.PE.Win32Resources.Builder;
namespace AsmResolver.DotNet.Bundles
@@ -287,6 +287,60 @@ public string GenerateDeterministicBundleID()
.Replace('/', '_');
}
+ ///
+ /// Determines the runtime that the assemblies in the bundle are targeting.
+ ///
+ /// The runtime.
+ /// Occurs when the runtime could not be determined.
+ public DotNetRuntimeInfo GetTargetRuntime()
+ {
+ return TryGetTargetRuntime(out var runtime)
+ ? runtime
+ : throw new ArgumentException("Could not determine the target runtime for the bundle");
+ }
+
+ ///
+ /// Attempts to determine the runtime that the assemblies in the bundle are targeting.
+ ///
+ /// When the method returns true, contains the target runtime.
+ /// true if the runtime could be determined, false otherwise.
+ public bool TryGetTargetRuntime(out DotNetRuntimeInfo targetRuntime)
+ {
+ // Try find the runtimeconfig.json file.
+ for (int i = 0; i < Files.Count; i++)
+ {
+ var file = Files[i];
+ if (file.Type == BundleFileType.RuntimeConfigJson)
+ {
+ var config = RuntimeConfiguration.FromJson(Encoding.UTF8.GetString(file.GetData()));
+ if (config is not {RuntimeOptions.TargetFrameworkMoniker: { } tfm})
+ continue;
+
+ if (DotNetRuntimeInfo.TryParseMoniker(tfm, out targetRuntime))
+ return true;
+ }
+ }
+
+ // If it is not present, make a best effort guess based on the bundle file format version.
+ switch (MajorVersion)
+ {
+ case 1:
+ targetRuntime = new DotNetRuntimeInfo(DotNetRuntimeInfo.NetCoreApp, new Version(3, 1));
+ return true;
+
+ case 2:
+ targetRuntime = new DotNetRuntimeInfo(DotNetRuntimeInfo.NetCoreApp, new Version(5, 0));
+ return true;
+
+ case 6:
+ targetRuntime = new DotNetRuntimeInfo(DotNetRuntimeInfo.NetCoreApp, new Version(6, 0));
+ return true;
+ }
+
+ targetRuntime = default;
+ return false;
+ }
+
///
/// Constructs a new application host file based on the bundle manifest.
///
diff --git a/src/AsmResolver.DotNet/DotNetCoreAssemblyResolver.cs b/src/AsmResolver.DotNet/DotNetCoreAssemblyResolver.cs
index 2bf0ed76d..b30c35585 100644
--- a/src/AsmResolver.DotNet/DotNetCoreAssemblyResolver.cs
+++ b/src/AsmResolver.DotNet/DotNetCoreAssemblyResolver.cs
@@ -3,6 +3,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using AsmResolver.DotNet.Config.Json;
+using AsmResolver.DotNet.Serialized;
using AsmResolver.IO;
namespace AsmResolver.DotNet
@@ -35,6 +36,17 @@ public DotNetCoreAssemblyResolver(IFileService fileService, Version runtimeVersi
{
}
+ ///
+ /// Creates a new .NET Core assembly resolver, by attempting to autodetect the current .NET or .NET Core
+ /// installation directory.
+ ///
+ /// The parameters to use while reading the assembly.
+ /// The version of .NET to target.
+ public DotNetCoreAssemblyResolver(ModuleReaderParameters readerParameters, Version runtimeVersion)
+ : this(readerParameters, null, runtimeVersion, DotNetCorePathProvider.Default)
+ {
+ }
+
///
/// Creates a new .NET Core assembly resolver, by attempting to autodetect the current .NET or .NET Core
/// installation directory.
@@ -81,7 +93,23 @@ public DotNetCoreAssemblyResolver(
RuntimeConfiguration? configuration,
Version? fallbackVersion,
DotNetCorePathProvider pathProvider)
- : base(fileService)
+ : this(new ModuleReaderParameters(fileService), configuration, fallbackVersion, pathProvider)
+ {
+ }
+
+ ///
+ /// Creates a new .NET Core assembly resolver.
+ ///
+ /// The parameters to use while reading the assembly.
+ /// The runtime configuration to use, or null if no configuration is available.
+ /// The version of .NET or .NET Core to use when no (valid) configuration is provided.
+ /// The installation directory of .NET Core.
+ public DotNetCoreAssemblyResolver(
+ ModuleReaderParameters readerParameters,
+ RuntimeConfiguration? configuration,
+ Version? fallbackVersion,
+ DotNetCorePathProvider pathProvider)
+ : base(readerParameters)
{
if (fallbackVersion is null)
throw new ArgumentNullException(nameof(fallbackVersion));
diff --git a/src/AsmResolver.DotNet/DotNetFrameworkAssemblyResolver.cs b/src/AsmResolver.DotNet/DotNetFrameworkAssemblyResolver.cs
index 6bd043431..ce9ce3425 100644
--- a/src/AsmResolver.DotNet/DotNetFrameworkAssemblyResolver.cs
+++ b/src/AsmResolver.DotNet/DotNetFrameworkAssemblyResolver.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using AsmResolver.DotNet.Serialized;
using AsmResolver.IO;
namespace AsmResolver.DotNet
@@ -25,7 +26,15 @@ public DotNetFrameworkAssemblyResolver()
///
/// The service to use for reading files from the disk.
public DotNetFrameworkAssemblyResolver(IFileService fileService)
- : base(fileService)
+ : this(new ModuleReaderParameters(fileService))
+ {
+ }
+
+ ///
+ /// Creates a new default assembly resolver.
+ ///
+ public DotNetFrameworkAssemblyResolver(ModuleReaderParameters readerParameters)
+ : base(readerParameters)
{
DetectGacDirectories();
}
@@ -92,7 +101,12 @@ private void DetectMonoGacDirectories()
.FirstOrDefault();
if (mostRecentMonoDirectory is not null)
+ {
SearchDirectories.Add(mostRecentMonoDirectory);
+ string facadesDirectory = Path.Combine(mostRecentMonoDirectory, "Facades");
+ if (Directory.Exists(facadesDirectory))
+ SearchDirectories.Add(facadesDirectory);
+ }
}
private void AddGacDirectories(string windowsGac, string? prefix)
diff --git a/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs b/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs
index 00d733f67..28d6e36bb 100644
--- a/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs
+++ b/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs
@@ -7,7 +7,7 @@ namespace AsmResolver.DotNet
///
/// Provides information about a target runtime.
///
- public readonly struct DotNetRuntimeInfo
+ public readonly struct DotNetRuntimeInfo : IEquatable
{
///
/// The target framework name used by applications targeting .NET and .NET Core.
@@ -26,6 +26,14 @@ public readonly struct DotNetRuntimeInfo
private static readonly Regex FormatRegex = new(@"([a-zA-Z.]+)\s*,\s*Version=v(\d+\.\d+)");
+ private static readonly Regex NetFxMonikerRegex = new(@"net(\d)(\d)(\d?)");
+
+ private static readonly Regex NetCoreAppMonikerRegex = new(@"netcoreapp(\d)\.(\d)");
+
+ private static readonly Regex NetStandardMonikerRegex = new(@"netstandard(\d)\.(\d)");
+
+ private static readonly Regex NetMonikerRegex = new(@"net(\d+)\.(\d+)");
+
///
/// Creates a new instance of the structure.
///
@@ -99,6 +107,45 @@ public static bool TryParse(string frameworkName, out DotNetRuntimeInfo info)
return true;
}
+ ///
+ /// Parses the target framework moniker as provided in a .runtimeconfig.json file.
+ ///
+ /// The moniker
+ /// The parsed version info.
+ public static DotNetRuntimeInfo ParseMoniker(string moniker)
+ {
+ return TryParseMoniker(moniker, out var info) ? info : throw new FormatException();
+ }
+
+ ///
+ /// Attempts to parse the target framework moniker as provided in a .runtimeconfig.json file.
+ ///
+ /// The moniker
+ /// The parsed version info.
+ /// true if the provided name was in the correct format, false otherwise.
+ public static bool TryParseMoniker(string moniker, out DotNetRuntimeInfo info)
+ {
+ info = default;
+ string runtime;
+
+ Match match;
+ if ((match = NetMonikerRegex.Match(moniker)).Success)
+ runtime = NetCoreApp;
+ else if ((match = NetCoreAppMonikerRegex.Match(moniker)).Success)
+ runtime = NetCoreApp;
+ else if ((match = NetStandardMonikerRegex.Match(moniker)).Success)
+ runtime = NetStandard;
+ else if ((match = NetFxMonikerRegex.Match(moniker)).Success)
+ runtime = NetFramework;
+ else
+ return false;
+
+ var version = new Version(int.Parse(match.Groups[1].Value), int.Parse(match.Groups[2].Value));
+
+ info = new DotNetRuntimeInfo(runtime, version);
+ return true;
+ }
+
///
/// Obtains a reference to the default core lib reference of this runtime.
///
@@ -108,5 +155,26 @@ public static bool TryParse(string frameworkName, out DotNetRuntimeInfo info)
///
public override string ToString() => $"{Name},Version=v{Version}";
+
+ ///
+ public bool Equals(DotNetRuntimeInfo other)
+ {
+ return Name == other.Name && Version.Equals(other.Version);
+ }
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ return obj is DotNetRuntimeInfo other && Equals(other);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (Name.GetHashCode() * 397) ^ Version.GetHashCode();
+ }
+ }
}
}
diff --git a/src/AsmResolver.DotNet/ModuleDefinition.cs b/src/AsmResolver.DotNet/ModuleDefinition.cs
index 3330de4d0..975497cb4 100644
--- a/src/AsmResolver.DotNet/ModuleDefinition.cs
+++ b/src/AsmResolver.DotNet/ModuleDefinition.cs
@@ -4,7 +4,6 @@
using System.IO;
using System.Linq;
using System.Reflection;
-using System.Runtime.InteropServices;
using System.Threading;
using AsmResolver.Collections;
using AsmResolver.DotNet.Builder;
@@ -62,7 +61,7 @@ public class ModuleDefinition :
/// The module.
/// Occurs when the image does not contain a valid .NET metadata directory.
public static ModuleDefinition FromBytes(byte[] buffer) =>
- FromImage(PEImage.FromBytes(buffer));
+ FromBytes(buffer, new ModuleReaderParameters());
///
/// Reads a .NET module from the provided input buffer.
@@ -99,15 +98,25 @@ public static ModuleDefinition FromFile(string filePath, ModuleReaderParameters
/// The portable executable file to load.
/// The module.
/// Occurs when the image does not contain a valid .NET metadata directory.
- public static ModuleDefinition FromFile(IInputFile file) => FromImage(PEImage.FromFile(file));
+ public static ModuleDefinition FromFile(IInputFile file) => FromFile(file, new ModuleReaderParameters());
///
/// Reads a .NET module from the provided input file.
///
/// The portable executable file to load.
+ /// The parameters to use while reading the module.
/// The module.
/// Occurs when the image does not contain a valid .NET metadata directory.
- public static ModuleDefinition FromFile(IPEFile file) => FromImage(PEImage.FromFile(file));
+ public static ModuleDefinition FromFile(IInputFile file, ModuleReaderParameters readerParameters) =>
+ FromImage(PEImage.FromFile(file, readerParameters.PEReaderParameters), readerParameters);
+
+ ///
+ /// Reads a .NET module from the provided input file.
+ ///
+ /// The portable executable file to load.
+ /// The module.
+ /// Occurs when the image does not contain a valid .NET metadata directory.
+ public static ModuleDefinition FromFile(IPEFile file) => FromFile(file, new ModuleReaderParameters());
///
/// Reads a .NET module from the provided input file.
@@ -250,8 +259,9 @@ public static ModuleDefinition FromImage(IPEImage peImage)
public static ModuleDefinition FromImage(IPEImage peImage, ModuleReaderParameters readerParameters) =>
new SerializedModuleDefinition(peImage, readerParameters);
- // Disable non-nullable property initialization warnings for the CorLibTypeFactory and MetadataResolver
- // properties. These are expected to be initialized by constructors that use this base constructor.
+ // Disable non-nullable property initialization warnings for the CorLibTypeFactory, RuntimeContext and
+ // MetadataResolver properties. These are expected to be initialized by constructors that use this base
+ // constructor.
#pragma warning disable 8618
///
@@ -295,7 +305,9 @@ public ModuleDefinition(Utf8String? name)
Name = name;
CorLibTypeFactory = CorLibTypeFactory.CreateMscorlib40TypeFactory(this);
- MetadataResolver = new DefaultMetadataResolver(new DotNetFrameworkAssemblyResolver());
+ OriginalTargetRuntime = DetectTargetRuntime();
+ RuntimeContext = new RuntimeContext(OriginalTargetRuntime);
+ MetadataResolver = new DefaultMetadataResolver(RuntimeContext.AssemblyResolver);
TopLevelTypes.Add(new TypeDefinition(null, TypeDefinition.ModuleTypeName, 0));
}
@@ -310,13 +322,10 @@ public ModuleDefinition(string? name, AssemblyReference corLib)
{
Name = name;
- var importer = new ReferenceImporter(this);
- corLib = (AssemblyReference) importer.ImportScope(corLib);
-
- CorLibTypeFactory = new CorLibTypeFactory(corLib);
-
+ CorLibTypeFactory = new CorLibTypeFactory(corLib.ImportWith(DefaultImporter));
OriginalTargetRuntime = DetectTargetRuntime();
- MetadataResolver = new DefaultMetadataResolver(CreateAssemblyResolver(UncachedFileService.Instance));
+ RuntimeContext = new RuntimeContext(OriginalTargetRuntime);
+ MetadataResolver = new DefaultMetadataResolver(RuntimeContext.AssemblyResolver);
TopLevelTypes.Add(new TypeDefinition(null, TypeDefinition.ModuleTypeName, 0));
}
@@ -342,7 +351,16 @@ public virtual IDotNetDirectory? DotNetDirectory
} = null;
///
- /// Gets the runtime that this module targeted upon creation or reading.
+ /// Gets the object describing the current active runtime context the module is loaded in.
+ ///
+ public RuntimeContext RuntimeContext
+ {
+ get;
+ protected set;
+ }
+
+ ///
+ /// Gets the runtime that this module was targeted for at compile-time.
///
public DotNetRuntimeInfo OriginalTargetRuntime
{
@@ -1203,48 +1221,6 @@ protected DotNetRuntimeInfo DetectTargetRuntime()
: CorLibTypeFactory.ExtractDotNetRuntimeInfo();
}
- ///
- /// Creates an assembly resolver based on the corlib reference.
- ///
- /// The resolver.
- protected IAssemblyResolver CreateAssemblyResolver(IFileService fileService)
- {
- string? directory = !string.IsNullOrEmpty(FilePath)
- ? Path.GetDirectoryName(FilePath)
- : null;
-
- var runtime = OriginalTargetRuntime;
-
- AssemblyResolverBase resolver;
- switch (runtime.Name)
- {
- case DotNetRuntimeInfo.NetFramework:
- case DotNetRuntimeInfo.NetStandard
- when string.IsNullOrEmpty(DotNetCorePathProvider.DefaultInstallationPath):
- resolver = new DotNetFrameworkAssemblyResolver(fileService);
- break;
-
- case DotNetRuntimeInfo.NetStandard
- when DotNetCorePathProvider.Default.TryGetLatestStandardCompatibleVersion(
- runtime.Version, out var coreVersion):
- resolver = new DotNetCoreAssemblyResolver(fileService, coreVersion);
- break;
-
- case DotNetRuntimeInfo.NetCoreApp:
- resolver = new DotNetCoreAssemblyResolver(fileService, runtime.Version);
- break;
-
- default:
- resolver = new DotNetFrameworkAssemblyResolver(fileService);
- break;
- }
-
- if (!string.IsNullOrEmpty(directory))
- resolver.SearchDirectories.Add(directory!);
-
- return resolver;
- }
-
///
public override string ToString() => Name ?? string.Empty;
diff --git a/src/AsmResolver.DotNet/RuntimeContext.cs b/src/AsmResolver.DotNet/RuntimeContext.cs
new file mode 100644
index 000000000..77435698c
--- /dev/null
+++ b/src/AsmResolver.DotNet/RuntimeContext.cs
@@ -0,0 +1,113 @@
+using System;
+using System.Reflection;
+using AsmResolver.DotNet.Bundles;
+using AsmResolver.DotNet.Serialized;
+using AsmResolver.IO;
+
+namespace AsmResolver.DotNet
+{
+ ///
+ /// Describes a context in which a .NET runtime is active.
+ ///
+ public class RuntimeContext
+ {
+ ///
+ /// Creates a new runtime context.
+ ///
+ /// The target runtime version.
+ public RuntimeContext(DotNetRuntimeInfo targetRuntime)
+ : this(targetRuntime, new ModuleReaderParameters(new ByteArrayFileService()))
+ {
+ }
+
+ ///
+ /// Creates a new runtime context.
+ ///
+ /// The target runtime version.
+ /// The parameters to use when reading modules in this context.
+ public RuntimeContext(DotNetRuntimeInfo targetRuntime, ModuleReaderParameters readerParameters)
+ {
+ TargetRuntime = targetRuntime;
+ DefaultReaderParameters = new ModuleReaderParameters(readerParameters) {RuntimeContext = this};
+ AssemblyResolver = CreateAssemblyResolver(targetRuntime, DefaultReaderParameters);
+ }
+
+ ///
+ /// Creates a new runtime context.
+ ///
+ /// The target runtime version.
+ /// The assembly resolver to use when resolving assemblies into this context.
+ public RuntimeContext(DotNetRuntimeInfo targetRuntime, IAssemblyResolver assemblyResolver)
+ {
+ TargetRuntime = targetRuntime;
+ DefaultReaderParameters = new ModuleReaderParameters(new ByteArrayFileService()) {RuntimeContext = this};
+ AssemblyResolver = assemblyResolver;
+ }
+
+ ///
+ /// Creates a new runtime context for the provided bundled application.
+ ///
+ /// The bundle to create the runtime context for.
+ public RuntimeContext(BundleManifest manifest)
+ : this(manifest, new ModuleReaderParameters(new ByteArrayFileService()))
+ {
+ }
+
+ ///
+ /// Creates a new runtime context.
+ ///
+ /// The bundle to create the runtime context for.
+ /// The parameters to use when reading modules in this context.
+ public RuntimeContext(BundleManifest manifest, ModuleReaderParameters readerParameters)
+ {
+ TargetRuntime = manifest.GetTargetRuntime();
+ DefaultReaderParameters = new ModuleReaderParameters(readerParameters) {RuntimeContext = this};
+ AssemblyResolver = new BundleAssemblyResolver(manifest, readerParameters);
+ }
+
+ ///
+ /// Gets the runtime version this context is targeting.
+ ///
+ public DotNetRuntimeInfo TargetRuntime
+ {
+ get;
+ }
+
+ ///
+ /// Gets the default parameters that are used for reading .NET modules in the context.
+ ///
+ public ModuleReaderParameters DefaultReaderParameters
+ {
+ get;
+ }
+
+ ///
+ /// Gets the assembly resolver that the context uses to resolve assemblies.
+ ///
+ public IAssemblyResolver AssemblyResolver
+ {
+ get;
+ }
+
+ private static IAssemblyResolver CreateAssemblyResolver(
+ DotNetRuntimeInfo runtime,
+ ModuleReaderParameters readerParameters)
+ {
+ switch (runtime.Name)
+ {
+ case DotNetRuntimeInfo.NetFramework:
+ case DotNetRuntimeInfo.NetStandard when string.IsNullOrEmpty(DotNetCorePathProvider.DefaultInstallationPath):
+ return new DotNetFrameworkAssemblyResolver(readerParameters);
+
+ case DotNetRuntimeInfo.NetStandard when DotNetCorePathProvider.Default.TryGetLatestStandardCompatibleVersion(runtime.Version, out var coreVersion):
+ return new DotNetCoreAssemblyResolver(readerParameters, coreVersion);
+
+ case DotNetRuntimeInfo.NetCoreApp:
+ return new DotNetCoreAssemblyResolver(readerParameters, runtime.Version);
+
+ default:
+ return new DotNetFrameworkAssemblyResolver(readerParameters);
+ }
+ }
+ }
+}
diff --git a/src/AsmResolver.DotNet/Serialized/DefaultMethodBodyReader.cs b/src/AsmResolver.DotNet/Serialized/DefaultMethodBodyReader.cs
index 07dbb19ea..39d7fb846 100644
--- a/src/AsmResolver.DotNet/Serialized/DefaultMethodBodyReader.cs
+++ b/src/AsmResolver.DotNet/Serialized/DefaultMethodBodyReader.cs
@@ -12,6 +12,11 @@ namespace AsmResolver.DotNet.Serialized
///
public class DefaultMethodBodyReader : IMethodBodyReader
{
+ ///
+ /// Gets the singleton instance of the class.
+ ///
+ public static DefaultMethodBodyReader Instance { get; } = new();
+
///
public virtual MethodBody? ReadMethodBody(ModuleReaderContext context, MethodDefinition owner, in MethodDefinitionRow row)
{
diff --git a/src/AsmResolver.DotNet/Serialized/ModuleReaderContext.cs b/src/AsmResolver.DotNet/Serialized/ModuleReaderContext.cs
index 737eab418..92e109817 100644
--- a/src/AsmResolver.DotNet/Serialized/ModuleReaderContext.cs
+++ b/src/AsmResolver.DotNet/Serialized/ModuleReaderContext.cs
@@ -25,7 +25,7 @@ public ModuleReaderContext(IPEImage image, SerializedModuleDefinition parentModu
{
Image = image ?? throw new ArgumentNullException(nameof(image));
ParentModule = parentModule ?? throw new ArgumentNullException(nameof(parentModule));
- Parameters = parameters ?? throw new ArgumentNullException(nameof(parameters));
+ Parameters = new ModuleReaderParameters(parameters) ?? throw new ArgumentNullException(nameof(parameters));
// Both CLR and CoreCLR implement a slightly different loading procedure for EnC metadata.
// While the difference is very subtle, it has a slight effect on which streams are selected
diff --git a/src/AsmResolver.DotNet/Serialized/ModuleReaderParameters.cs b/src/AsmResolver.DotNet/Serialized/ModuleReaderParameters.cs
index 1696903a8..29a30dcc9 100644
--- a/src/AsmResolver.DotNet/Serialized/ModuleReaderParameters.cs
+++ b/src/AsmResolver.DotNet/Serialized/ModuleReaderParameters.cs
@@ -1,4 +1,4 @@
-using System;
+using AsmResolver.IO;
using AsmResolver.PE;
using AsmResolver.PE.DotNet.Metadata;
@@ -13,6 +13,28 @@ public class ModuleReaderParameters
/// Initializes the default module read parameters.
///
public ModuleReaderParameters()
+ {
+ MethodBodyReader = DefaultMethodBodyReader.Instance;
+ FieldRvaDataReader = AsmResolver.PE.DotNet.Metadata.FieldRvaDataReader.Instance;
+ PEReaderParameters = new PEReaderParameters();
+ }
+
+ ///
+ /// Initializes the module read parameters with a file service.
+ ///
+ /// The context the module should be read in.
+ public ModuleReaderParameters(RuntimeContext context)
+ : this(context.DefaultReaderParameters)
+ {
+ RuntimeContext = context;
+ }
+
+ ///
+ /// Initializes the module read parameters with a file service.
+ ///
+ /// The file service to use when reading the file and dependencies.
+ public ModuleReaderParameters(IFileService fileService)
+ : this(null, new PEReaderParameters {FileService = fileService})
{
}
@@ -40,6 +62,16 @@ public ModuleReaderParameters(string? workingDirectory)
/// The working directory of the modules to read.
/// The object responsible for recording parser errors.
public ModuleReaderParameters(string? workingDirectory, IErrorListener errorListener)
+ : this(workingDirectory, new PEReaderParameters(errorListener))
+ {
+ }
+
+ ///
+ /// Initializes the module read parameters with a working directory.
+ ///
+ /// The working directory of the modules to read.
+ /// The parameters to use while reading the assembly and its dependencies.
+ public ModuleReaderParameters(string? workingDirectory, PEReaderParameters readerParameters)
{
if (workingDirectory is not null)
{
@@ -47,7 +79,23 @@ public ModuleReaderParameters(string? workingDirectory, IErrorListener errorList
ModuleResolver = new DirectoryNetModuleResolver(workingDirectory, this);
}
- PEReaderParameters.ErrorListener = errorListener;
+ MethodBodyReader = DefaultMethodBodyReader.Instance;
+ FieldRvaDataReader = AsmResolver.PE.DotNet.Metadata.FieldRvaDataReader.Instance;
+ PEReaderParameters = readerParameters;
+ }
+
+ ///
+ /// Clones the provided module reader parameters.
+ ///
+ /// The parameters to clone.
+ public ModuleReaderParameters(ModuleReaderParameters readerParameters)
+ {
+ WorkingDirectory = readerParameters.WorkingDirectory;
+ ModuleResolver = readerParameters.ModuleResolver;
+ MethodBodyReader = readerParameters.MethodBodyReader;
+ FieldRvaDataReader = readerParameters.FieldRvaDataReader;
+ PEReaderParameters = readerParameters.PEReaderParameters;
+ RuntimeContext = readerParameters.RuntimeContext;
}
///
@@ -74,7 +122,7 @@ public IMethodBodyReader MethodBodyReader
{
get;
set;
- } = new DefaultMethodBodyReader();
+ }
///
/// Gets or sets the field initial value reader.
@@ -83,7 +131,7 @@ public IFieldRvaDataReader FieldRvaDataReader
{
get;
set;
- } = new FieldRvaDataReader();
+ }
///
/// Gets or sets the parameters used for parsing a PE file into a PE image.
@@ -95,6 +143,15 @@ public PEReaderParameters PEReaderParameters
{
get;
set;
- } = new();
+ }
+
+ ///
+ /// Gets or sets the runtime context to load the module in, or null if a new context is to be created.
+ ///
+ public RuntimeContext? RuntimeContext
+ {
+ get;
+ set;
+ }
}
}
diff --git a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs
index 4d5ff6282..d76fb01a9 100644
--- a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs
+++ b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
+using System.IO;
using AsmResolver.DotNet.Collections;
using AsmResolver.DotNet.Signatures.Types;
using AsmResolver.PE;
@@ -70,15 +71,34 @@ public SerializedModuleDefinition(IPEImage peImage, ModuleReaderParameters reade
OriginalTargetRuntime = DetectTargetRuntime();
// Initialize metadata resolution engines.
- var resolver = CreateAssemblyResolver(readerParameters.PEReaderParameters.FileService);
- if (!string.IsNullOrEmpty(readerParameters.WorkingDirectory)
- && resolver is AssemblyResolverBase resolverBase
- && !resolverBase.SearchDirectories.Contains(readerParameters.WorkingDirectory!))
+ if (readerParameters.RuntimeContext is { } runtimeContext)
{
- resolverBase.SearchDirectories.Add(readerParameters.WorkingDirectory!);
+ RuntimeContext = runtimeContext;
+ }
+ else
+ {
+ RuntimeContext = new RuntimeContext(OriginalTargetRuntime, readerParameters);
+
+ if (RuntimeContext.AssemblyResolver is AssemblyResolverBase resolver)
+ {
+ // Add current file's directory as a search directory (if present).
+ if (!string.IsNullOrEmpty(peImage.FilePath)
+ && Path.GetDirectoryName(peImage.FilePath) is { } directory
+ && !resolver.SearchDirectories.Contains(directory))
+ {
+ resolver.SearchDirectories.Add(directory);
+ }
+
+ // Add current working directory as a search directory (if present).
+ if (!string.IsNullOrEmpty(readerParameters.WorkingDirectory)
+ && !resolver.SearchDirectories.Contains(readerParameters.WorkingDirectory!))
+ {
+ resolver.SearchDirectories.Add(readerParameters.WorkingDirectory!);
+ }
+ }
}
- MetadataResolver = new DefaultMetadataResolver(resolver);
+ MetadataResolver = new DefaultMetadataResolver(RuntimeContext.AssemblyResolver);
// Prepare lazy RID lists.
_fieldLists = new LazyRidListRelation(metadata, TableIndex.Field, TableIndex.TypeDef,
@@ -271,7 +291,7 @@ protected override IList GetExportedTypes()
}
///
- protected override string GetRuntimeVersion() => ReaderContext.Metadata!.VersionString;
+ protected override string GetRuntimeVersion() => ReaderContext.Metadata.VersionString;
///
protected override IManagedEntryPoint? GetManagedEntryPoint()
diff --git a/src/AsmResolver.PE/Certificates/DefaultCertificateReader.cs b/src/AsmResolver.PE/Certificates/DefaultCertificateReader.cs
index 60992e9ab..f20240465 100644
--- a/src/AsmResolver.PE/Certificates/DefaultCertificateReader.cs
+++ b/src/AsmResolver.PE/Certificates/DefaultCertificateReader.cs
@@ -8,6 +8,11 @@ namespace AsmResolver.PE.Certificates
///
public class DefaultCertificateReader : ICertificateReader
{
+ ///
+ /// Gets the singleton instance of the class.
+ ///
+ public static DefaultCertificateReader Instance { get; } = new();
+
///
public AttributeCertificate ReadCertificate(
PEReaderContext context,
diff --git a/src/AsmResolver.PE/Debug/DefaultDebugDataReader.cs b/src/AsmResolver.PE/Debug/DefaultDebugDataReader.cs
index 6af91f361..219aad8b0 100644
--- a/src/AsmResolver.PE/Debug/DefaultDebugDataReader.cs
+++ b/src/AsmResolver.PE/Debug/DefaultDebugDataReader.cs
@@ -8,6 +8,11 @@ namespace AsmResolver.PE.Debug
///
public class DefaultDebugDataReader : IDebugDataReader
{
+ ///
+ /// Gets the singleton instance of the class.
+ ///
+ public static DefaultDebugDataReader Instance { get; } = new();
+
///
public IDebugDataSegment? ReadDebugData(PEReaderContext context, DebugDataType type,
ref BinaryStreamReader reader)
diff --git a/src/AsmResolver.PE/DotNet/Metadata/FieldRvaDataReader.cs b/src/AsmResolver.PE/DotNet/Metadata/FieldRvaDataReader.cs
index 07cb6d674..e51018fe7 100644
--- a/src/AsmResolver.PE/DotNet/Metadata/FieldRvaDataReader.cs
+++ b/src/AsmResolver.PE/DotNet/Metadata/FieldRvaDataReader.cs
@@ -13,6 +13,11 @@ namespace AsmResolver.PE.DotNet.Metadata
///
public class FieldRvaDataReader : IFieldRvaDataReader
{
+ ///
+ /// Gets the singleton instance of the class.
+ ///
+ public static FieldRvaDataReader Instance { get; } = new();
+
///
public ISegment? ResolveFieldData(
IErrorListener listener,
diff --git a/src/AsmResolver.PE/DotNet/ReadyToRun/DefaultReadyToRunSectionReader.cs b/src/AsmResolver.PE/DotNet/ReadyToRun/DefaultReadyToRunSectionReader.cs
index f1e95f821..6e6062b9e 100644
--- a/src/AsmResolver.PE/DotNet/ReadyToRun/DefaultReadyToRunSectionReader.cs
+++ b/src/AsmResolver.PE/DotNet/ReadyToRun/DefaultReadyToRunSectionReader.cs
@@ -9,6 +9,11 @@ namespace AsmResolver.PE.DotNet.ReadyToRun
///
public class DefaultReadyToRunSectionReader : IReadyToRunSectionReader
{
+ ///
+ /// Gets the singleton instance of the class.
+ ///
+ public static DefaultReadyToRunSectionReader Instance { get; } = new();
+
///
public IReadyToRunSection ReadSection(PEReaderContext context, ReadyToRunSectionType type, ref BinaryStreamReader reader)
{
diff --git a/src/AsmResolver.PE/PEImage.cs b/src/AsmResolver.PE/PEImage.cs
index 7f2b49481..dc1eadcf6 100644
--- a/src/AsmResolver.PE/PEImage.cs
+++ b/src/AsmResolver.PE/PEImage.cs
@@ -145,8 +145,17 @@ public static IPEImage FromReader(in BinaryStreamReader reader, PEMappingMode mo
/// The file representing the PE.
/// The PE image that was opened.
/// Occurs when the file does not follow the PE file format.
- public static IPEImage FromFile(IInputFile inputFile) =>
- FromFile(PEFile.FromFile(inputFile), new PEReaderParameters());
+ public static IPEImage FromFile(IInputFile inputFile) => FromFile(inputFile, new PEReaderParameters());
+
+ ///
+ /// Opens a PE image from an input file object.
+ ///
+ /// The file representing the PE.
+ /// The parameters to use while reading the PE image.
+ /// The PE image that was opened.
+ /// Occurs when the file does not follow the PE file format.
+ public static IPEImage FromFile(IInputFile inputFile, PEReaderParameters readerParameters) =>
+ FromFile(PEFile.FromFile(inputFile), readerParameters);
///
/// Opens a PE image from a PE file object.
diff --git a/src/AsmResolver.PE/PEReaderParameters.cs b/src/AsmResolver.PE/PEReaderParameters.cs
index 32a48c4ad..145cd11e4 100644
--- a/src/AsmResolver.PE/PEReaderParameters.cs
+++ b/src/AsmResolver.PE/PEReaderParameters.cs
@@ -26,10 +26,11 @@ public PEReaderParameters()
/// The object responsible for recording parser errors.
public PEReaderParameters(IErrorListener errorListener)
{
- MetadataStreamReader = new DefaultMetadataStreamReader();
- DebugDataReader = new DefaultDebugDataReader();
- CertificateReader = new DefaultCertificateReader();
- ReadyToRunSectionReader = new DefaultReadyToRunSectionReader();
+ MetadataStreamReader = DefaultMetadataStreamReader.Instance;
+ DebugDataReader = DefaultDebugDataReader.Instance;
+ CertificateReader = DefaultCertificateReader.Instance;
+ FileService = UncachedFileService.Instance;
+ ReadyToRunSectionReader = DefaultReadyToRunSectionReader.Instance;
ErrorListener = errorListener ?? throw new ArgumentNullException(nameof(errorListener));
}
@@ -77,7 +78,7 @@ public IFileService FileService
{
get;
set;
- } = UncachedFileService.Instance;
+ }
///
/// Gets or sets the object to use for reading ReadyToRun metadata sections from the disk while reading the
diff --git a/src/AsmResolver/IO/ByteArrayFileService.cs b/src/AsmResolver/IO/ByteArrayFileService.cs
index 02816d600..4551bed50 100644
--- a/src/AsmResolver/IO/ByteArrayFileService.cs
+++ b/src/AsmResolver/IO/ByteArrayFileService.cs
@@ -22,6 +22,17 @@ public IInputFile OpenFile(string filePath)
return _files.GetOrAdd(filePath, x => new ByteArrayInputFile(x));
}
+ ///
+ /// Assigns a file path to a byte array and opens it.
+ ///
+ /// The file path.
+ /// The contents of the file.
+ /// The opened file.
+ public IInputFile OpenBytesAsFile(string filePath, byte[] contents)
+ {
+ return _files.GetOrAdd(filePath, p => new ByteArrayInputFile(p, contents, 0));
+ }
+
///
public void InvalidateFile(string filePath) => _files.TryRemove(filePath, out _);
diff --git a/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj b/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj
index 0f275278b..fc1b5225f 100644
--- a/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj
+++ b/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj
@@ -12,11 +12,13 @@
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/test/AsmResolver.Benchmarks/TypeResolutionBenchmark.cs b/test/AsmResolver.Benchmarks/TypeResolutionBenchmark.cs
new file mode 100644
index 000000000..bd612489d
--- /dev/null
+++ b/test/AsmResolver.Benchmarks/TypeResolutionBenchmark.cs
@@ -0,0 +1,32 @@
+using AsmResolver.DotNet;
+using AsmResolver.DotNet.Serialized;
+using AsmResolver.DotNet.TestCases.Methods;
+using AsmResolver.DotNet.TestCases.Types;
+using AsmResolver.IO;
+using BenchmarkDotNet.Attributes;
+
+namespace AsmResolver.Benchmarks;
+
+[MemoryDiagnoser]
+public class TypeResolutionBenchmark
+{
+ [Params(false, true)]
+ public bool UsingRuntimeContext { get; set; }
+
+ [Benchmark]
+ public void ResolveSystemObjectInTwoAssemblies()
+ {
+ var service = new ByteArrayFileService();
+ var parameters = new ModuleReaderParameters(service);
+
+ var module1 = ModuleDefinition.FromFile(typeof(Class).Assembly.Location, parameters);
+
+ if (UsingRuntimeContext)
+ parameters.RuntimeContext = module1.RuntimeContext;
+
+ var module2 = ModuleDefinition.FromFile(typeof(SingleMethod).Assembly.Location, parameters);
+
+ _ = module1.CorLibTypeFactory.Object.Resolve();
+ _ = module2.CorLibTypeFactory.Object.Resolve();
+ }
+}
diff --git a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs
index 6cf4bc39e..812f76f6e 100644
--- a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs
+++ b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs
@@ -5,6 +5,7 @@
using System.Runtime.InteropServices;
using System.Text;
using AsmResolver.DotNet.Bundles;
+using AsmResolver.DotNet.Serialized;
using AsmResolver.IO;
using AsmResolver.PE;
using AsmResolver.PE.DotNet.Cil;
@@ -94,6 +95,46 @@ public void WriteBundleManifestV6Windows()
"Hello, World!\n");
}
+ [Fact]
+ public void DetectNetCoreApp31Bundle()
+ {
+ var manifest = BundleManifest.FromBytes(Properties.Resources.HelloWorld_SingleFile_V1);
+ Assert.Equal(
+ new DotNetRuntimeInfo(DotNetRuntimeInfo.NetCoreApp, new Version(3, 1)),
+ manifest.GetTargetRuntime()
+ );
+ }
+
+ [Fact]
+ public void DetectNet50Bundle()
+ {
+ var manifest = BundleManifest.FromBytes(Properties.Resources.HelloWorld_SingleFile_V2);
+ Assert.Equal(
+ new DotNetRuntimeInfo(DotNetRuntimeInfo.NetCoreApp, new Version(5, 0)),
+ manifest.GetTargetRuntime()
+ );
+ }
+
+ [Fact]
+ public void DetectNet60Bundle()
+ {
+ var manifest = BundleManifest.FromBytes(Properties.Resources.HelloWorld_SingleFile_V6);
+ Assert.Equal(
+ new DotNetRuntimeInfo(DotNetRuntimeInfo.NetCoreApp, new Version(6, 0)),
+ manifest.GetTargetRuntime()
+ );
+ }
+
+ [Fact]
+ public void DetectNet80Bundle()
+ {
+ var manifest = BundleManifest.FromBytes(Properties.Resources.HelloWorld_SingleFile_V6_WithDependency);
+ Assert.Equal(
+ new DotNetRuntimeInfo(DotNetRuntimeInfo.NetCoreApp, new Version(8, 0)),
+ manifest.GetTargetRuntime()
+ );
+ }
+
[SkippableFact]
public void MarkFilesAsCompressed()
{
@@ -359,5 +400,19 @@ private static void AssertBundlesAreEqual(BundleManifest manifest, BundleManifes
Assert.Equal(file.GetData(), newFile.GetData());
}
}
+
+ [Fact]
+ public void BundleRuntimeContext()
+ {
+ var manifest = BundleManifest.FromBytes(Properties.Resources.HelloWorld_SingleFile_V6_WithDependency);
+ var context = new RuntimeContext(manifest);
+
+ var module = ModuleDefinition.FromBytes(
+ manifest.Files.First(x => x.RelativePath == "MainApp.dll").GetData(),
+ new ModuleReaderParameters(context));
+
+ var resolved = module.AssemblyReferences.First(x => x.Name == "Library").Resolve();
+ Assert.NotNull(resolved);
+ }
}
}
diff --git a/test/AsmResolver.DotNet.Tests/DotNetRuntimeInfoTest.cs b/test/AsmResolver.DotNet.Tests/DotNetRuntimeInfoTest.cs
index 206a808cf..1a1b60272 100644
--- a/test/AsmResolver.DotNet.Tests/DotNetRuntimeInfoTest.cs
+++ b/test/AsmResolver.DotNet.Tests/DotNetRuntimeInfoTest.cs
@@ -1,3 +1,4 @@
+using System;
using System.Reflection;
using AsmResolver.DotNet.Signatures;
using Xunit;
@@ -6,6 +7,22 @@ namespace AsmResolver.DotNet.Tests
{
public class DotNetRuntimeInfoTest
{
+ [Theory]
+ [InlineData(".NETFramework,Version=v2.0", DotNetRuntimeInfo.NetFramework, 2, 0)]
+ [InlineData(".NETFramework,Version=v3.5", DotNetRuntimeInfo.NetFramework, 3, 5)]
+ [InlineData(".NETFramework,Version=v4.0", DotNetRuntimeInfo.NetFramework, 4, 0)]
+ [InlineData(".NETStandard,Version=v1.0", DotNetRuntimeInfo.NetStandard, 1, 0)]
+ [InlineData(".NETStandard,Version=v2.0", DotNetRuntimeInfo.NetStandard, 2, 0)]
+ [InlineData(".NETCoreApp,Version=v2.0", DotNetRuntimeInfo.NetCoreApp, 2, 0)]
+ [InlineData(".NETCoreApp,Version=v5.0", DotNetRuntimeInfo.NetCoreApp, 5, 0)]
+ public void Parse(string name, string expectedFramework, int major, int minor)
+ {
+ Assert.Equal(
+ new DotNetRuntimeInfo(expectedFramework, new Version(major, minor)),
+ DotNetRuntimeInfo.Parse(name)
+ );
+ }
+
[Theory]
[InlineData(".NETFramework,Version=v2.0", "mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
[InlineData(".NETFramework,Version=v3.5", "mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
@@ -19,7 +36,26 @@ public void DefaultCorLib(string name, string expectedCorLib)
Assert.Equal(
new ReflectionAssemblyDescriptor(new AssemblyName(expectedCorLib)),
(AssemblyDescriptor) DotNetRuntimeInfo.Parse(name).GetDefaultCorLib(),
- SignatureComparer.Default);
+ SignatureComparer.Default
+ );
+ }
+
+ [Theory]
+ [InlineData("net20", DotNetRuntimeInfo.NetFramework, 2, 0)]
+ [InlineData("net35", DotNetRuntimeInfo.NetFramework, 3, 5)]
+ [InlineData("net40", DotNetRuntimeInfo.NetFramework, 4, 0)]
+ [InlineData("net47", DotNetRuntimeInfo.NetFramework, 4, 7)]
+ [InlineData("net472", DotNetRuntimeInfo.NetFramework, 4, 7)]
+ [InlineData("netstandard2.0", DotNetRuntimeInfo.NetStandard, 2, 0)]
+ [InlineData("netcoreapp2.1", DotNetRuntimeInfo.NetCoreApp, 2,1)]
+ [InlineData("net5.0", DotNetRuntimeInfo.NetCoreApp, 5, 0)]
+ [InlineData("net8.0", DotNetRuntimeInfo.NetCoreApp, 8, 0)]
+ public void ParseMoniker(string tfm, string expectedFramework, int major, int minor)
+ {
+ Assert.Equal(
+ new DotNetRuntimeInfo(expectedFramework, new Version(major, minor)),
+ DotNetRuntimeInfo.ParseMoniker(tfm)
+ );
}
}
}
diff --git a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs
index e36534bf9..cd22ef17d 100644
--- a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs
+++ b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs
@@ -164,6 +164,13 @@ public static byte[] HelloWorld_SingleFile_V6_WithResources {
}
}
+ public static byte[] HelloWorld_SingleFile_V6_WithDependency {
+ get {
+ object obj = ResourceManager.GetObject("HelloWorld_SingleFile_V6_WithDependency", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
public static byte[] HelloWorld_UnusualNestedTypeRefOrder {
get {
object obj = ResourceManager.GetObject("HelloWorld_UnusualNestedTypeRefOrder", resourceCulture);
diff --git a/test/AsmResolver.DotNet.Tests/Properties/Resources.resx b/test/AsmResolver.DotNet.Tests/Properties/Resources.resx
index d0c0c7189..f7b1908b2 100644
--- a/test/AsmResolver.DotNet.Tests/Properties/Resources.resx
+++ b/test/AsmResolver.DotNet.Tests/Properties/Resources.resx
@@ -69,6 +69,9 @@
..\Resources\HelloWorld.SingleFile.v6.WithResources.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+ ..\Resources\HelloWorld.SingleFile.v6.WithDependency.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
..\Resources\HelloWorld.UnusualNestedTypeRefOrder.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
diff --git a/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.SingleFile.v6.WithDependency.exe b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.SingleFile.v6.WithDependency.exe
new file mode 100755
index 000000000..19fb20328
Binary files /dev/null and b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.SingleFile.v6.WithDependency.exe differ
diff --git a/test/AsmResolver.DotNet.Tests/RuntimeContextTest.cs b/test/AsmResolver.DotNet.Tests/RuntimeContextTest.cs
new file mode 100644
index 000000000..19f855181
--- /dev/null
+++ b/test/AsmResolver.DotNet.Tests/RuntimeContextTest.cs
@@ -0,0 +1,100 @@
+using System;
+using AsmResolver.DotNet.Serialized;
+using AsmResolver.DotNet.TestCases.Methods;
+using AsmResolver.DotNet.TestCases.Types;
+using AsmResolver.IO;
+using Xunit;
+
+namespace AsmResolver.DotNet.Tests
+{
+ public class RuntimeContextTest
+ {
+ [Fact]
+ public void ResolveDependencyShouldUseSameRuntimeContext()
+ {
+ var main = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld);
+ var dependency = main.CorLibTypeFactory.CorLibScope.GetAssembly()!.Resolve()!.ManifestModule!;
+
+ Assert.Same(main.RuntimeContext, dependency.RuntimeContext);
+ }
+
+ [Fact]
+ public void ResolveDependencyShouldUseSameFileService()
+ {
+ var service = new ByteArrayFileService();
+ service.OpenBytesAsFile("HelloWorld.dll", Properties.Resources.HelloWorld);
+
+ var main = ModuleDefinition.FromFile("HelloWorld.dll", new ModuleReaderParameters(service));
+ var dependency = main.CorLibTypeFactory.CorLibScope.GetAssembly()!.Resolve()!.ManifestModule!;
+
+ Assert.Contains(main.FilePath, service.GetOpenedFiles());
+ Assert.Contains(dependency.FilePath, service.GetOpenedFiles());
+ }
+
+ [Fact]
+ public void DetectNetFrameworkContext()
+ {
+ var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld);
+ Assert.Equal(
+ new DotNetRuntimeInfo(DotNetRuntimeInfo.NetFramework, new Version(4, 0)),
+ module.RuntimeContext.TargetRuntime
+ );
+ }
+
+ [Fact]
+ public void DetectNetCoreAppContext()
+ {
+ var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_NetCore);
+ Assert.Equal(
+ new DotNetRuntimeInfo(DotNetRuntimeInfo.NetCoreApp, new Version(2, 2)),
+ module.RuntimeContext.TargetRuntime
+ );
+ }
+
+ [Fact]
+ public void ForceNetFXLoadAsNetCore()
+ {
+ var context = new RuntimeContext(new DotNetRuntimeInfo(DotNetRuntimeInfo.NetCoreApp, new Version(3, 1)));
+ var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld, new ModuleReaderParameters(context));
+
+ Assert.Equal(context.TargetRuntime, module.RuntimeContext.TargetRuntime);
+ Assert.IsAssignableFrom(module.MetadataResolver.AssemblyResolver);
+ }
+
+ [Fact]
+ public void ForceNetStandardLoadAsNetFx()
+ {
+ var context = new RuntimeContext(new DotNetRuntimeInfo(DotNetRuntimeInfo.NetFramework, new Version(4, 8)));
+ var module = ModuleDefinition.FromFile(typeof(Class).Assembly.Location, new ModuleReaderParameters(context));
+
+ Assert.Equal(context.TargetRuntime, module.RuntimeContext.TargetRuntime);
+ Assert.Equal("mscorlib", module.CorLibTypeFactory.Object.Resolve()?.Module?.Assembly?.Name);
+ }
+
+ [Fact]
+ public void ForceNetStandardLoadAsNetCore()
+ {
+ var context = new RuntimeContext(new DotNetRuntimeInfo(DotNetRuntimeInfo.NetCoreApp, new Version(3, 1)));
+ var module = ModuleDefinition.FromFile(typeof(Class).Assembly.Location, new ModuleReaderParameters(context));
+
+ Assert.Equal(context.TargetRuntime, module.RuntimeContext.TargetRuntime);
+ Assert.Equal("System.Private.CoreLib", module.CorLibTypeFactory.Object.Resolve()?.Module?.Assembly?.Name);
+ }
+
+ [Fact]
+ public void ResolveSameDependencyInSameContextShouldResultInSameAssembly()
+ {
+ var module1 = ModuleDefinition.FromFile(typeof(Class).Assembly.Location);
+ var module2 = ModuleDefinition.FromFile(typeof(SingleMethod).Assembly.Location, new ModuleReaderParameters
+ {
+ RuntimeContext = module1.RuntimeContext
+ });
+
+ var object1 = module1.CorLibTypeFactory.Object.Resolve();
+ var object2 = module2.CorLibTypeFactory.Object.Resolve();
+
+ Assert.NotNull(object1);
+ Assert.Same(object1, object2);
+ }
+ }
+}