Fody / Costura

Embed references as resources

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Assembly attributes make apps using Costura crash at startup when `CreateTemporaryAssemblies="true"`

0xced opened this issue · comments

Please check all of the platforms you are having the issue on (if platform is not listed, it is not supported)

  • WPF
  • UWP
  • iOS
  • Android
  • .NET Standard
  • .NET Core

Component

Costura 5.3.0

Version of OS(s) listed above with issue

Windows 10

Steps to Reproduce

Project layout:

├───MyApp
│       FodyWeavers.xml
│       FodyWeavers.xsd
│       MyApp.csproj
│       Program.cs
│
└───MyLibrary
        MyAssemblyAttribute.cs
        MyLibrary.csproj

MyApp.csproj:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net472</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Costura.Fody" Version="5.3.0" PrivateAssets="all" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\MyLibrary\MyLibrary.csproj" />
  </ItemGroup>

</Project>

FodyWeavers.xml:

<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
  <Costura CreateTemporaryAssemblies="true" />
</Weavers>

Program.cs:

using System;

[assembly: MyLibrary.MyAssemblyAttribute]

namespace MyApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

MyLibrary.csproj:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net472</TargetFramework>
  </PropertyGroup>

</Project>

MyAssemblyAttribute:

using System;

namespace MyLibrary
{
    [AttributeUsage(AttributeTargets.Assembly)]
    public class MyAssemblyAttribute : Attribute
    {
    }
}

Run this project with dotnet run --project MyApp\MyApp.csproj

Expected Behavior

The program runs and Hello World! is printed on the console output.

Actual Behavior

The program crashes with the following exception:

Unhandled Exception: System.TypeInitializationException: The type initializer for '<Module>' threw an exception. ---> System.IO.FileNotFoundException: Could not load file or assembly 'MyLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
   at System.ModuleHandle.ResolveType(RuntimeModule module, Int32 typeToken, IntPtr* typeInstArgs, Int32 typeInstCount, IntPtr* methodInstArgs, Int32 methodInstCount, ObjectHandleOnStack type)
   at System.ModuleHandle.ResolveTypeHandleInternal(RuntimeModule module, Int32 typeToken, RuntimeTypeHandle[] typeInstantiationContext, RuntimeTypeHandle[] methodInstantiationContext)
   at System.Reflection.RuntimeModule.ResolveType(Int32 metadataToken, Type[] genericTypeArguments, Type[] genericMethodArguments)
   at System.Reflection.CustomAttribute.FilterCustomAttributeRecord(CustomAttributeRecord caRecord, MetadataImport scope, Assembly& lastAptcaOkAssembly, RuntimeModule decoratedModule, MetadataToken decoratedToken, RuntimeType attributeFilterType, Boolean mustBeInheritable, Object[] attributes, IList derivedAttributes, RuntimeType& attributeType, IRuntimeMethodInfo& ctor, Boolean& ctorHasParameters, Boolean& isVarArg)
   at System.Reflection.CustomAttribute.GetCustomAttributes(RuntimeModule decoratedModule, Int32 decoratedMetadataToken, Int32 pcaCount, RuntimeType attributeFilterType, Boolean mustBeInheritable, IList derivedAttributes, Boolean isDecoratedTargetSecurityTransparent)
   at System.Reflection.CustomAttribute.GetCustomAttributes(RuntimeAssembly assembly, RuntimeType caType)
   at System.Attribute.GetCustomAttributes(Assembly element, Type attributeType, Boolean inherit)
   at System.Attribute.GetCustomAttribute(Assembly element, Type attributeType, Boolean inherit)
   at System.Reflection.CustomAttributeExtensions.GetCustomAttribute(Assembly element, Type attributeType)
   at Costura.AssemblyLoader.Attach()
   at .cctor()
   --- End of inner exception stack trace ---

Additional notes

I discovered this issue while using the Microsoft.CrmSdk.XrmTooling.CoreAssembly package and generating Early Bound Xrm classes with the Early Bound Generator which adds this attribute in the generated organization service context class:

[assembly: Microsoft.Xrm.Sdk.Client.ProxyTypesAssemblyAttribute()]

I need CreateTemporaryAssemblies="true" because the CrmSdk absolutely wants to access the assembly on disk. 😠

When implementing #638 I did not realize that GetCustomAttribute could potentially call into the assembly resolving mechanism.

So this is a catch-22 situation where we need to access the TargetFrameworkAttribute but accessing it fires the AssemblyResolve before we can set it up. 😕

Workarounds

There are two workarounds I can think of.

  1. Don't use CreateTemporaryAssemblies="true".
  2. Don't use assembly attributes.

Unfortunately, depending on the situation, neither of them may be possible.

Possible solution

I have an idea to solve this issue but it's so complicated I'm not sure it's worth trying to implement. It would go like this:

  1. Register an AssemblyResolve event that resolves assembly from embedded resources (same as in the ILtemplate)
  2. Execute the code that ensures the target framework is properly set on the current domain
  3. Unload any assembly that was loaded as a result of setting the target framework (is unloading even possible?)
  4. Register the standard AssemblyResolve from the ILTemplateWithTempAssembly that loads dlls from the disk cache.

This is really convoluted and I hope I can think of a better solution. For the mean time I have deleted Microsoft.Xrm.Sdk.Client.ProxyTypesAssemblyAttribute which is not absolutely required for my usage.

In .NET Core, this would be possible by creating a specific load context for the assemblies to be loaded. We do something similar in Orc.Extensibility where we check each (potential) extension in a separate load context. Once accepted, we can load them into the primary application so they can interact with the system.

In full .NET, this would require separate app domains, which at this stage I would not invest in (if I were you). Are the attributes in the main app or in depending libraries? In the 2nd case, you could determine the order or use LoadAssembliesOnStartup to force loading of specific assemblies first.

Another option that might work: use reflection only to determine the attribute. I think, in this case, it will not need the attribute stuff (but we should test before making this assumption).

I just got bitten by this again on another project, so I'm looking at it again. Could you please elaborate on this?

use reflection only to determine the attribute

I'm not sure to understand what you mean.

I found a workaround that allows you to use both CreateTemporaryAssemblies="true" and custom assembly attributes. Once again, LoadAtModuleInit="false" + CosturaUtility.Initialize() in the program's static constructor saves the day!

Is this something we can detect at compile time (e.g. if we use this combination, fail the build and explain to use LoadAtModuleInit + CosturaUtility?