MonoMod / MonoMod.Common

Common code used by MonoMod and other .NET modding libraries. Not to be confused with MonoMod.Utils (for mods).
MIT License
75 stars 33 forks source link

Failed to create proxy when inside an AssemblyLoadContext in net6 #26

Open cc004 opened 2 years ago

cc004 commented 2 years ago

stacktrace

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
 ---> System.IO.FileLoadException: Could not load file or assembly 'MonoMod.Common, Version=22.3.5.1, Culture=neutral, PublicKeyToken=null'. Operation is not supported. (0x80131515)
File name: 'MonoMod.Common, Version=22.3.5.1, Culture=neutral, PublicKeyToken=null'
 ---> System.NotSupportedException: Resolving to a collectible assembly is not supported.
   at System.Reflection.RuntimeAssembly.GetType(QCallAssembly assembly, String name, Boolean throwOnError, Boolean ignoreCase, ObjectHandleOnStack type, ObjectHandleOnStack keepAlive, ObjectHandleOnStack assemblyLoadContext)
   at System.Reflection.RuntimeAssembly.GetType(String name, Boolean throwOnError, Boolean ignoreCase)
   at System.Reflection.Assembly.GetType(String name)
   at MonoMod.Utils.Cil.ILGeneratorShim.ILGeneratorBuilder.GenerateProxy()
   at MonoMod.Utils.Cil.ILGeneratorShim.GetProxy()
   at HarmonyLib.MethodPatcher..ctor(MethodBase original, MethodBase source, List`1 prefixes, List`1 postfixes, List`1 transpilers, List`1 finalizers, Boolean debug)
   at HarmonyLib.PatchFunctions.UpdateWrapper(MethodBase original, PatchInfo patchInfo)
   at HarmonyLib.PatchProcessor.Patch()
   at Localizer.Utils.Patch(Harmony instance, String class, String method, Boolean exactMatch, HarmonyMethod prefix, HarmonyMethod postfix, HarmonyMethod transpiler) in Localizer\Helpers\Utils.cs:line 426
   at Localizer.Localizer..ctor() in Localizer.cs:line 59
   at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean wrapExceptions)
   --- End of inner exception stack trace ---
   at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean wrapExceptions)
   at Terraria.ModLoader.Core.AssemblyManager.Instantiate(ModLoadContext mod) in tModLoader\Terraria\ModLoader\Core\AssemblyManager.cs:line 188
   at Terraria.ModLoader.Core.AssemblyManager.<>c__DisplayClass11_0.<InstantiateMods>b__1(ModLoadContext mod) in tModLoader\Terraria\ModLoader\Core\AssemblyManager.cs:line 260
   at System.Linq.Enumerable.SelectListIterator`2.ToList()
   at Terraria.ModLoader.Core.AssemblyManager.InstantiateMods(List`1 modsToLoad, CancellationToken token) in tModLoader\Terraria\ModLoader\Core\AssemblyManager.cs:line 258
   at Terraria.ModLoader.Core.ModOrganizer.LoadMods(CancellationToken token) in tModLoader\Terraria\ModLoader\Core\ModOrganizer.cs:line 243
   at Terraria.ModLoader.ModLoader.Load(CancellationToken token) in tModLoader\Terraria\ModLoader\ModLoader.cs:line 116
CyberAndrii commented 1 year ago

This happens when assembly MonoMod.Utils is loaded in a collectible AssemblyLoadContext which prevents the non-collectible MonoMod.Utils.Cil.ILGeneratorProxy to be loaded.

Potential fix is to load MonoMod.Utils.Cil.ILGeneratorProxy in the same AssemblyLoadContext as MonoMod.Utils. This line should look like this (simplified, in reality we will need to use reflection on older target frameworks):

var utilsAssembly = Assembly.GetExecutingAssembly();
var context = AssemblyLoadContext.GetLoadContext(utilsAssembly);
asm = context.LoadFromStream(copy);

Without this fix it is a bug which got patched in the .NET Runtime 7 and now throws the exception OP posted.

nike4613 commented 1 year ago

I'm honestly not sure supporting MonoMod being loaded in a collectible ALC is a good idea. In fact, I'm fairly certain that it is a terrible idea. MonoMod relies on being able to root objects in statics, and must be able to keep detours alive and correct. If the runtime were to unload it, that could (in fact, would) leave detours in invalid states and be liable to inducing runtime crashes.

If we did want to support it, the only safe support we can have is allocating a GC handle to some MonoMod object as long as there are references in the DynamicReferenceManager. This should effectively root all relevant MonoMod types, preventing them from being collected. This does also defeat the purpose of unloadable ALCs though.


I do think it's a good idea to load our dynamically generated assemblies in the same ALC that created them though. That would require some more work than just the proposed change however.