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

Linker doesn't preserve type forwarder for a nested type in a second type forwarder #2359

Closed
vitek-karas opened this issue Nov 9, 2021 · 7 comments

Comments

@vitek-karas
Copy link
Member

vitek-karas commented Nov 9, 2021

Repro:

app.csproj

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

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <TrimMode>copyused</TrimMode>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Azure.Identity" Version="1.5.0" />
    <PackageReference Include="Azure.ResourceManager.Resources" Version="1.0.0-beta.2" />
  </ItemGroup>
</Project>

app.cs

using Azure.ResourceManager;

typeof(ArmClientOptions).Assembly.GetTypes();

Publish the app with trimming enabled and run it. It fails with Could not load type 'Enumerator' from assembly 'Microsoft.Bcl.AsyncInterfaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'

The reason is that Azure.ResourceManager.dll has a type reference to a System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable``1/Enumerator in Microsoft.Bcl.AsyncInterfaces. This assembly has a type forwarder for this nested type which points to netstandard. And netstandard has a type forward to corelib.

After trimming only the type forwarder in Microsoft.Bcl.AsyncInterfaces is kept. The one in netstandard is removed. Note that the parent type is kept in both cases, it's only the nested type forwarder which is removed.

Also notes that this runs the linker in copyused mode.

@vitek-karas
Copy link
Member Author

The actual problem is that setup like this, the project trims the assemblies as a mix of "copy" and "copyused" actions. This is bound to break. "copyused" makes the assumption that all type refs are rewritten to point to implementation assembly (as it doesn't preserve type forwarders by default and it doesn't mark them). On the other hand "copy" can't rewrite the type refs.

The workaround is to set TrimmerDefaultAction=copyused.

@marek-safar
Copy link
Contributor

This looks to me like something worth servicing fix, thought?

@vitek-karas
Copy link
Member Author

I don't think this is worth servicing. I honestly even question if we should fix it.

Going forward we don't want people to use "copyused". It's not the default anymore, the only way to get there is through TrimMode=copyused. So maybe what we should do is simply add a warning that you're using "copyused" and there are problems.

As for this specific issue - as far as I can tell, type forwarders were always problematic. It's just that .NET 6 behaves differently from .NET 5, but they're both wrong in certain cases.

I guess it's a discussion of importance - how important is it to make it work, given it's not the default and we generally don't encourage people to use it.

@marek-safar
Copy link
Contributor

ok, I missed there is custom setting of

<TrimMode>copyused</TrimMode>

Should we start issuing a warning about this mode in .NET7 so we can remove it in .NET8?

vitek-karas added a commit to vitek-karas/linker that referenced this issue Nov 16, 2021
The problematic scenario is if there's a "copy" assembly with a forwarder to a "link" assembly with a forwarder. The forwarder is for a nested class.
Test for dotnet#2359.

Also improved the test infra to print out errors from ilasm if they happen.
@vitek-karas
Copy link
Member Author

This is actually a bug for "link" mode as well.

The scenario is:

  • "copy" assembly test which has a reference to Namespace.Type.Nested (nested type on Type) and Namespace.OtherType.Nested from CopyForwarder assembly.
  • "copy" assembly CopyForwarder which has a type forwarder for Namespace.Type.Nested and Namespace.OtherType.Nested which points to LinkForwarder
  • "link" assembly LinkForwarder which has a type forwarder for Namespace.Type.Nested which points to Implementation but also has a type forwarder for Namespace.OtherType.Nested which points to Implementation.
  • "link" assembly Implementation with the right implementations for the type forwards.

Trimming this setup will actually remove one of the nested type forwarders in the LinkForwarder (it should keep both).
This is a combination of two problems:

  • There's a bug in the linker in
    markingHelpers.MarkForwardedScope (new TypeReference (exportedType.Namespace, exportedType.Name, assembly.MainModule, exportedType.Scope));
    . We create a type ref for a forwarder, but the way it's done it will only work for non-nested type forwarders. Nested type forwarders will end up with a TypeRef("", "Nested", module, assembly) with no DeclaringType. So they look like a type without a namespace.
  • There's a bug in Cecil: Type references with empty namespace can resolve to nested type forwarders jbevain/cecil#812. Where such a type ref actually resolves to the first nested type forwarder with the right name. So linker doesn't fail, it just ends up marking potentially the wrong type forwarder.

The bug exists both in "copyused" and "link" modes.

@vitek-karas
Copy link
Member Author

Test for this issue in the linker is #2372.
Test for the issue in Cecil is in vitek-karas/cecil@1d9104a.

Note that fixing the problem in linker doesn't depend on the Cecil fix. The Cecil bug just makes it so that the bug in the linker doesn't cause failures. If the linker creates a correct type ref with declaring type set, it will work even on current cecil correctly.

@vitek-karas vitek-karas added this to the .NET 6.0 milestone Nov 16, 2021
vitek-karas added a commit that referenced this issue Nov 17, 2021
…2372)

The problematic scenario is if there's a "copy" assembly with a forwarder to a "link" assembly with a forwarder. The forwarder is for a nested class.
Test for #2359.

Also improved the test infra to print out errors from ilasm if they happen.
vitek-karas added a commit to vitek-karas/linker that referenced this issue Nov 22, 2021
…otnet#2372)

The problematic scenario is if there's a "copy" assembly with a forwarder to a "link" assembly with a forwarder. The forwarder is for a nested class.
Test for dotnet#2359.

Also improved the test infra to print out errors from ilasm if they happen.
@vitek-karas
Copy link
Member Author

The fix for this has been merged into the 6.0.2xx branch and should appear in 6.0.200 SDK soon.

agocke pushed a commit to dotnet/runtime that referenced this issue Nov 16, 2022
…otnet/linker#2372)

The problematic scenario is if there's a "copy" assembly with a forwarder to a "link" assembly with a forwarder. The forwarder is for a nested class.
Test for dotnet/linker#2359.

Also improved the test infra to print out errors from ilasm if they happen.


Commit migrated from dotnet/linker@57574f1
agocke pushed a commit to dotnet/runtime that referenced this issue Nov 16, 2022
…otnet/linker#2372)

The problematic scenario is if there's a "copy" assembly with a forwarder to a "link" assembly with a forwarder. The forwarder is for a nested class.
Test for dotnet/linker#2359.

Also improved the test infra to print out errors from ilasm if they happen.


Commit migrated from dotnet/linker@70cd7ed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants