Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
2 of 6 tasks
0xced opened this issue May 6, 2021 · 4 comments

Comments

@0xced
Copy link
Contributor

0xced commented May 6, 2021

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.

@GeertvanHorrik
Copy link
Member

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).

@0xced
Copy link
Contributor Author

0xced commented Aug 26, 2021

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.

@0xced
Copy link
Contributor Author

0xced commented Aug 30, 2021

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!

0xced added a commit to 0xced/Costura that referenced this issue Aug 30, 2021
This makes TempFileTests.ExecutableRunsSuccessfully fail
@GeertvanHorrik
Copy link
Member

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants