dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.17k stars 4.72k forks source link

`AssemblyName.FullName` doesn't show `PublicKeyToken` portion when the `AssemblyName` instance is returned from `AssemblyName.GetAssemblyName` #66785

Closed daxian-dbw closed 2 years ago

daxian-dbw commented 2 years ago

Description

For an assembly that is not strong-named, AssemblyName.FullName doesn't show PublicKeyToken=null portion when the AssemblyName instance is returned from AssemblyName.GetAssemblyName. This is a regression from .NET 6.

This regression caused one of PowerShell test to fail, which was disabled for the time being: https://github.com/PowerShell/PowerShell/blob/master/test/xUnit/csharp/test_NativeInterop.cs#L20

However, after the assembly is loaded, the AssemblyName instance returned from Assembly.GetName() works as expected (and as before).

Reproduction Steps

  1. Create a non-strong-named assembly, here is the code for doing that:

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Runtime.InteropServices;
    using System.Runtime.Loader;
    using System.Management.Automation;
    
    using Microsoft.CodeAnalysis;
    using Microsoft.CodeAnalysis.CSharp;
    using Microsoft.CodeAnalysis.Emit;
    using Microsoft.CodeAnalysis.Text;
    
    namespace MyApp // Note: actual namespace depends on the project name.
    {
        public class Program
        {
            public static void Main(string[] args)
            {
                string tempDir = Path.Combine(Path.GetTempPath(), "TestLoadNativeInMemoryAssembly");
                string testDll = Path.Combine(tempDir, "test.dll");
    
                if (!File.Exists(testDll))
                {
                    Directory.CreateDirectory(tempDir);
                    bool result = CreateTestDll(testDll);
                }
    
                var asmName = AssemblyName.GetAssemblyName(testDll);
    
                Console.WriteLine($"Assembly: {testDll}");
                Console.WriteLine($"Assembly FullName: {asmName.FullName}");
            }
    
            private static bool CreateTestDll(string dllPath)
            {
                var parseOptions = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest);
                var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
    
                List<SyntaxTree> syntaxTrees = new();
                SourceText sourceText = SourceText.From("public class Utt { }");
                syntaxTrees.Add(CSharpSyntaxTree.ParseText(sourceText, parseOptions));
    
                var refs = new List<PortableExecutableReference> { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) };
                Compilation compilation = CSharpCompilation.Create(
                            Path.GetRandomFileName(),
                            syntaxTrees: syntaxTrees,
                            references: refs,
                            options: compilationOptions);
    
                using var fs = new FileStream(dllPath, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None);
                EmitResult emitResult = compilation.Emit(peStream: fs, options: null);
                return emitResult.Success;
            }
        }
    }
  2. Running the above code with .NET 7-preview.2, you will see the , PublicKeyToken=null part is missing from the Assembly FullName.

Expected behavior

The , PublicKeyToken=null is present, like in .NET 6 and prior .NET versions.

Actual behavior

The , PublicKeyToken=null portion is missing.

Here is what you will see in PowerShell that built with .NET 7-preview.2:

PS:20> [System.Reflection.AssemblyName]::GetAssemblyName("C:\arena\tmp\abc.dll").FullName
qkoexti2.h5p, Version=0.0.0.0, Culture=neutral
PS:15> $t = Add-Type -Path C:\arena\tmp\abc.dll -PassThru
PS:16> $t.Assembly.FullName
qkoexti2.h5p, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
PS:17> $n = $t.Assembly.GetName()
PS:18> $n.FullName
qkoexti2.h5p, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null

Here is the .NET version:

PS:23> [System.Environment]::Version

Major  Minor  Build  Revision
-----  -----  -----  --------
7      0      0      -1

Regression?

Yes. It's a regression to .NET 6 and prior versions.

Known Workarounds

No response

Configuration

No response

Other information

No response

ghost commented 2 years ago

Tagging subscribers to this area: @dotnet/area-system-reflection See info in area-owners.md if you want to be subscribed.

Issue Details
### Description For an assembly that is not strong-named, `AssemblyName.FullName` doesn't show `PublicKeyToken=null` portion when the `AssemblyName` instance is returned from `AssemblyName.GetAssemblyName`. This is a regression from .NET 6. However, after the assembly is loaded, the `AssemblyName` instance returned from `Assembly.GetName()` works as expected (and as before). ### Reproduction Steps 1. Create a non-strong-named assembly, here is the code for doing that: ```c# using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.Loader; using System.Management.Automation; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Text; namespace MyApp // Note: actual namespace depends on the project name. { public class Program { public static void Main(string[] args) { string tempDir = Path.Combine(Path.GetTempPath(), "TestLoadNativeInMemoryAssembly"); string testDll = Path.Combine(tempDir, "test.dll"); if (!File.Exists(testDll)) { Directory.CreateDirectory(tempDir); bool result = CreateTestDll(testDll); } var asmName = AssemblyName.GetAssemblyName(testDll); Console.WriteLine($"Assembly: {testDll}"); Console.WriteLine($"Assembly FullName: {asmName.FullName}"); } private static bool CreateTestDll(string dllPath) { var parseOptions = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest); var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); List syntaxTrees = new(); SourceText sourceText = SourceText.From("public class Utt { }"); syntaxTrees.Add(CSharpSyntaxTree.ParseText(sourceText, parseOptions)); var refs = new List { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) }; Compilation compilation = CSharpCompilation.Create( Path.GetRandomFileName(), syntaxTrees: syntaxTrees, references: refs, options: compilationOptions); using var fs = new FileStream(dllPath, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None); EmitResult emitResult = compilation.Emit(peStream: fs, options: null); return emitResult.Success; } } } ``` 2. Running the above code with .NET 7-preview.2, you will see the `, PublicKeyToken=null` part is missing from the `Assembly FullName`. ### Expected behavior The `, PublicKeyToken=null` is present, like in .NET 6 and prior .NET versions. ### Actual behavior The `, PublicKeyToken=null` portion is missing. Here is what you will see in PowerShell that built with .NET 7-preview.2: ``` PS:20> [System.Reflection.AssemblyName]::GetAssemblyName("C:\arena\tmp\abc.dll").FullName qkoexti2.h5p, Version=0.0.0.0, Culture=neutral ``` ``` PS:15> $t = Add-Type -Path C:\arena\tmp\abc.dll -PassThru PS:16> $t.Assembly.FullName qkoexti2.h5p, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null PS:17> $n = $t.Assembly.GetName() PS:18> $n.FullName qkoexti2.h5p, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null ``` Here is the .NET version: ``` PS:23> [System.Environment]::Version Major Minor Build Revision ----- ----- ----- -------- 7 0 0 -1 ``` ### Regression? Yes. It's a regression to .NET 6 and prior versions. ### Known Workarounds _No response_ ### Configuration _No response_ ### Other information _No response_
Author: daxian-dbw
Assignees: -
Labels: `area-System.Reflection`
Milestone: -
jkotas commented 2 years ago

cc @VSadov

VSadov commented 2 years ago

Likely introduced by the switch to managed implementation.

VSadov commented 2 years ago

This is a matter of visualizing missing PublicKeyToken - whether we write out PublicKeyToken=null or not if the key is missing.

It looks like 6.0 prints that and we should too, but I wonder if that is always the case or there are other hints to consider on whether to print this or not.

VSadov commented 2 years ago

Interestingly PublicKeyToken that is null is not visualized at all, but PublicKeyToken that is empty (i.e. Length == 0) is visualized as PublicKeyToken=null