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

ILTemplateWithTempAssembly interferes with AppContext switches initialization #633

Closed
2 of 6 tasks
0xced opened this issue Jan 11, 2021 · 5 comments · Fixed by #638
Closed
2 of 6 tasks

ILTemplateWithTempAssembly interferes with AppContext switches initialization #633

0xced opened this issue Jan 11, 2021 · 5 comments · Fixed by #638

Comments

@0xced
Copy link
Contributor

0xced commented Jan 11, 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

Actually, a console app on .NET Framework is enough to reproduce the issue.

Component

What component is this issue occurring in?

The Costura module initializer.

Version of Library

4.1.0

Version of OS(s) listed above with issue

Windows 10 Version 1909

Steps to Reproduce

  1. Create a console app and add Costura.Fody 4.1.0 and Fody 6.3.0 package references.
  • experiment.csproj
<Project Sdk="Microsoft.NET.Sdk">

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

  <ItemGroup>
    <PackageReference Include="Costura.Fody" Version="4.1.0" PrivateAssets="All" />
    <PackageReference Include="Fody" Version="6.3.0" PrivateAssets="All" />
  </ItemGroup>

</Project>
  • Program.cs
using System;

namespace experiment
{
    internal static class Program
    {
        private static void Main()
        {
            // Copied from System.AppContextDefaultValues
            var switchNames = new[]
            {
                "Switch.System.Globalization.NoAsyncCurrentCulture",
                "Switch.System.Globalization.EnforceJapaneseEraYearRanges",
                "Switch.System.Globalization.FormatJapaneseFirstYearAsANumber",
                "Switch.System.Globalization.EnforceLegacyJapaneseDateParsing",
                "Switch.System.Threading.ThrowExceptionIfDisposedCancellationTokenSource",
                "Switch.System.Diagnostics.EventSource.PreserveEventListnerObjectIdentity",
                "Switch.System.IO.UseLegacyPathHandling",
                "Switch.System.IO.BlockLongPaths",
                "Switch.System.Security.Cryptography.DoNotAddrOfCspParentWindowHandle",
                "Switch.System.Security.ClaimsIdentity.SetActorAsReferenceWhenCopyingClaimsIdentity",
                "Switch.System.Diagnostics.IgnorePortablePDBsInStackTraces",
                "Switch.System.Runtime.Serialization.UseNewMaxArraySize",
                "Switch.System.Runtime.Serialization.UseConcurrentFormatterTypeCache",
                "Switch.System.Threading.UseLegacyExecutionContextBehaviorUponUndoFailure",
                "Switch.System.Security.Cryptography.UseLegacyFipsThrow",
                "Switch.System.Runtime.InteropServices.DoNotMarshalOutByrefSafeArrayOnInvoke",
                "Switch.System.Threading.UseNetCoreTimer",
            };
            foreach (var switchName in switchNames)
            {
                if (AppContext.TryGetSwitch(switchName, out var isEnabled))
                {
                    Console.WriteLine($"{switchName} = {isEnabled}");
                }
            }
        }
    }
}
  • FodyWeavers.xml
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
  <Costura />
</Weavers>
  1. In a Developer Command Prompt, build and run the program:
MSBuild /restore experiment.csproj
bin\Debug\net472\experiment.exe

Here is the output which shows the default AppContext switch values when targeting .NET Framework 4.7.2

Switch.System.Security.Cryptography.UseLegacyFipsThrow = True
Switch.System.Runtime.InteropServices.DoNotMarshalOutByrefSafeArrayOnInvoke = True
  1. Modify FodyWeavers.xml to create temporary assemblies:
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
  <Costura CreateTemporaryAssemblies="true" />
</Weavers>
  1. In a Developer Command Prompt, build and run the program:
MSBuild /restore experiment.csproj
bin\Debug\net472\experiment.exe

Expected Behavior

The AppContext switch values should be the same as before, using CreateTemporaryAssemblies="true" should not impact the AppContext switches.

Actual Behavior

The AppContext switch values are different:

Switch.System.Globalization.NoAsyncCurrentCulture = True
Switch.System.Threading.ThrowExceptionIfDisposedCancellationTokenSource = True
Switch.System.IO.UseLegacyPathHandling = True
Switch.System.IO.BlockLongPaths = True
Switch.System.Security.Cryptography.DoNotAddrOfCspParentWindowHandle = True
Switch.System.Security.ClaimsIdentity.SetActorAsReferenceWhenCopyingClaimsIdentity = True
Switch.System.Diagnostics.IgnorePortablePDBsInStackTraces = True
Switch.System.Security.Cryptography.UseLegacyFipsThrow = True
Switch.System.Runtime.InteropServices.DoNotMarshalOutByrefSafeArrayOnInvoke = True

What's happening

When using CreateTemporaryAssemblies=true, ILTemplateWithTempAssembly is used instead of ILTemplate. ILTemplateWithTempAssembly calls Path.GetTempPath() in its Attach() method which is executed during module initialization. The call to Path.GetTempPath() eventually kicks in the AppContext switches initialization procedure, as shown in this stack trace:

AppContextDefaultValues.ParseTargetFrameworkName() at AppContextDefaultValues.cs:line 50
AppContextDefaultValues.PopulateDefaultValues() at AppContextDefaultValues.cs:line 41
AppContext.InitializeDefaultSwitchValues()
AppContext.TryGetSwitch()
static AppContextSwitches()
AppContextSwitches.get_BlockLongPaths()
Path.NormalizePath()
Path.GetFullPathInternal()
Path.GetTempPath()
AssemblyLoader.Attach() at obj\Debug\net472\ILTemplateWithTempAssembly.cs:line 31
static <Module>()

The ParseTargetFrameworkName() method accesses AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName. But this is too early: during the module initializer, AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName is not yet set and is still null. This causes the AppContext PopulateDefaultValues() to execute with a default (fallback) value of .NET Framework 4.0.0 setting wrong values for the AppContext switches.

Workaround

Using LoadAtModuleInit="false" in the Costura configuration in addition to CreateTemporaryAssemblies="true" then explicitly calling CosturaUtility.Initialize() in the Program static constructor works around this issue because AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName is properly initialized at that point.

Ideally, though, Costura should not interfere the the AppContext switches initialization code but I'm not sure what's the best way. Maybe the code that calls Path.GetTempPath() should be executed the first time ResolveAssembly is called? Maybe at that point AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName is properly set?

Additional information

I wrote some notes while investigating this issue. This issue became visible to me because of the wrong initialization of Switch.System.Diagnostics.IgnorePortablePDBsInStackTraces which resulted in the loss of source information in stack traces when embedding the debug symbols with <DebugType>embedded</DebugType>.

@GeertvanHorrik
Copy link
Member

GeertvanHorrik commented Jan 12, 2021

First of all, great research and great way to report a ticket, much appreciated.

If there is a valid workaround (which appears there is), we won't be making changes on this front. We don't want to go into a rabbit hole causing a lot of work for things we don't use ourselves.

As a side note: Costura is in maintenance mode: https://github.com/fody/costura#-read-this--package-is-in-maintenance-mode--read-this-

We will still maintain it, but very carefully select what changes we make to the code.

@0xced
Copy link
Contributor Author

0xced commented Jan 12, 2021

Thanks for your reply, Geert. I totally understand that Costura is in maintenance mode and that it's probably not worth trying to fix this, especially given that a rather clean workaround exists.

It might still be worth documenting that caveat in the README under the CreateTemporaryAssemblies and Native Libraries and PreloadOrder sections, what do you think?

@GeertvanHorrik
Copy link
Member

Great idea. Interested in doing a PR with these improvements?

0xced added a commit to 0xced/Costura that referenced this issue Jan 12, 2021
Explain how to workaround AppContext switches interference and mention issue Fody#633.
@0xced
Copy link
Contributor Author

0xced commented Jan 12, 2021

Yeah, done in #634.

@GeertvanHorrik
Copy link
Member

Thank you very much. I'm sorry we can't fix this in any other way, but we need to carefully review any resources we put into libraries supported by any of us (for free).

0xced added a commit to 0xced/Costura that referenced this issue Jan 15, 2021
Since Costura.Template targets .NET Standard 2.0 we test `AppContext.TargetFrameworkName == null` which translates to `AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName == null` on .NET Framework and `Assembly.GetEntryAssembly()?.GetCustomAttribute<TargetFrameworkAttribute>()?.FrameworkName == null` on .NET Core.

Fixes Fody#633
0xced added a commit to 0xced/Costura that referenced this issue Jan 17, 2021
Since Costura.Template targets .NET Standard 2.0 we test `AppContext.TargetFrameworkName == null` which translates to `AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName == null` on .NET Framework and `Assembly.GetEntryAssembly()?.GetCustomAttribute<TargetFrameworkAttribute>()?.FrameworkName == null` on .NET Core.

Fixes Fody#633
0xced added a commit to 0xced/Costura that referenced this issue Jan 17, 2021
Since Costura.Template targets .NET Standard 2.0 we test `AppContext.TargetFrameworkName == null` which translates to `AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName == null` on .NET Framework and `Assembly.GetEntryAssembly()?.GetCustomAttribute<TargetFrameworkAttribute>()?.FrameworkName == null` on .NET Core.

Fixes Fody#633
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

Successfully merging a pull request may close this issue.

2 participants