Washi1337/AsmResolver

.NET Runtime Contexts

Closed this issue · 0 comments

Problem Description

.NET binaries often depend on one or more DLL dependencies. These in turn may have dependencies on their own, all the way back to mscorlib (.NET Framework) or System.Private.Corlib (.NET Core / .NET). AsmResolver currently loads all modules independently of each other. Currently, this poses a couple problems:

  • Walking the dependency graph may result in loading the same dependency multiple times. Consider assembly A that depends on assembly B. When a type resolution is issued for a member reference defined in B from A, loading assembly B may trigger the load of mscorlib a second time given that assembly B also depends on mscorlib but has their own assembly resolver and thus their own assembly cache.

  • Different modules can target different .NET runtimes which may trigger different resolvers to be initialized. Consider a main assembly A targeting .NET Framework 4.6.x that depends on assembly B targeting .NET standard 2.0. By default, loading assembly B will initialize an instance of DotNetCoreAssemblyResolver. However, from the perspective of assembly A, a DotNetFrameworkAssemblyResolver should be initialized instead since the library would be loaded under .NET Framework as opposed to .NET Core. This can result in inconsistencies during type resolution and signature comparing. In particular, these inconsistencies may result in resolution scopes to be resolved multiple times during type signature comparisons (to determine equivalence), which is very slow.

  • AppHost bundles may have their own set of embedded dependencies. Not only may these dependencies be third-party that won't exist in the same source folder, these dependencies may also include a specific version of the BCL libraries themselves (e.g. in the case of single-file-host binaries). AsmResolver ideally should prefer loading assemblies embedded in the bundle (keeping in mind the runtimeconfig.json file as well), over the ones that can be found on the file system.

Proposal

The challenges above would be solved in the following manner:

  • Introduce a concept of runtime contexts. This would be similar to System.Runtime.Loader.AssemblyLoadContext but for binaries loaded statically by AsmResolver where .NET binaries are loaded and wired with a single assembly resolver based on the "root" assembly.

  • Add runtime context property to ModuleReaderParameters. When not null, the module will be loaded into this runtime context.

  • Add a get-only property ModuleDefinition::RuntimeContext exposing the context the module was loaded in. This is useful when manually opening multiple modules in the same load context, or when a user wants to get access to the "root" assembly easily.

  • Add an assembly resolver specifically for apphost/singlefilehost binaries. This is to facilitate the option of processing bundled files without having to extract them to the disk first. BundleManifest should also provide a RuntimeContext property for the loaded bundle, such that all assemblies embedded in the bundle can be easily loaded using the same load context as well.

Alternatives

The alternative for users right now is to manually assign the right reader parameters and assembly resolvers to each module that is being loaded, or to add all loaded assemblies to the assembly cache of each resolver. This, however, is very error-prone and is also highly susceptible to data-races given the nature of AsmResolvers lazy initialization.

Additional Context

This is a stepping stone for AsmResolver.Workspaces (#298).