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

[.NET Core 2.0.0-preview2-25407-01][Regression] ResourceManager.GetString(string) throw System.IO.FileNotFoundException #22308

Closed
daxian-dbw opened this issue Jun 15, 2017 · 13 comments · Fixed by dotnet/coreclr#12329
Assignees
Labels
area-System.Reflection blocking Marks issues that we want to fast track in order to unblock other important work bug
Milestone

Comments

@daxian-dbw
Copy link
Contributor

Summary

PowerShell Core recently moved to .NET Core 2.0.0-preview2-25407-01 with .NET Core SDK 2.0.0-preview2-006388. Then we found a regression in this .NET Core version -- the call to ResourceManager.GetString(string), which worked fine on .NET Core 2.0.0-preview1-002106-00, starts to throw System.IO.FileNotFoundException.

Impact

This is a blocking issue for us. It basically breaks PowerShell core.

Repro

An application that reads a resource string from a DLL

using System;
using System.Diagnostics;
using System.Reflection;
using System.Resources;

namespace temp
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length != 3 && args.Length != 4) {
                Console.WriteLine("Usage: test.exe <path-to-assembly> <resource-name> <resource-string-key> [-debug]");
                return;
            }

            var path = args[0];
            var rName = args[1];
            var rKey = args[2];

            if (args.Length == 4) {
                Console.WriteLine("PID: {0}", Process.GetCurrentProcess().Id);
                Console.ReadKey();
            }
            
            Console.WriteLine("Assembly:\n\t-- {0}", path);
            Console.WriteLine("Resource:\n\t-- {0}", rName);
            Console.WriteLine("StringKey:\n\t-- {0}", rKey);
            Assembly asm = Assembly.LoadFrom(path);

            var rm = new ResourceManager(rName, asm);
            try {
                string rStr = rm.GetString(rKey);
                Console.WriteLine("\n{0}:\n\t-- {1}", rKey, rStr);
            } catch (Exception ex) {
                Console.WriteLine("\n!!! {0} Caught:\n\t-- {1}", ex.GetType().FullName, ex.Message);
            }
        }
    }
}

Lib.dll is an example assembly that is built against .NET Core 2.0. We can see the embedded resource files in ildasm as follows:

image

The project to build Lib.dll is also attached for your reference: Lib.zip

Run the application to get a resource string:

dotnet run C:\tmp\Lib.dll Lib.Resources.CsvCommandStrings CannotSpecifyPathAndLiteralPath

Expected Behavior

With .NET Core 2.0.0-preview1-002106-00 (using .NET Core SDK 2.0.0-preview1-005952), the resource string corresponding to the key CannotSpecifyPathAndLiteralPath is retrieved back:

PS:34> dotnet run C:\tmp\Lib.dll Lib.Resources.CsvCommandStrings CannotSpecifyPathAndLiteralPath
Assembly:
        -- C:\tmp\Lib.dll
Resource:
        -- Lib.Resources.CsvCommandStrings
StringKey:
        -- CannotSpecifyPathAndLiteralPath

CannotSpecifyPathAndLiteralPath:
        -- You must specify either the -Path or -LiteralPath parameters, but not both.

Actual Behavior

With .NET Core 2.0.0-preview2-25407-01, System.IO.FileNotFoundException is thrown by ResourceManager.GetString(string):

PS:47> dotnet run C:\tmp\Lib.dll Lib.Resources.CsvCommandStrings CannotSpecifyPathAndLiteralPath
Assembly:
        -- C:\tmp\Lib.dll
Resource:
        -- Lib.Resources.CsvCommandStrings
StringKey:
        -- CannotSpecifyPathAndLiteralPath

!!! System.IO.FileNotFoundException Caught:
        -- Could not load file or assembly 'Lib.resources, Version=1.0.0.0, Culture=en-US, PublicKeyToken=null'. The system cannot find the file specified.
@tarekgh tarekgh self-assigned this Jun 15, 2017
@tarekgh
Copy link
Member

tarekgh commented Jun 15, 2017

CC @danmosemsft

@tarekgh
Copy link
Member

tarekgh commented Jun 16, 2017

@gkhanna79 @atsushikan could you please help with this regression? Please treat this as high priority as it is blocking PS.

What is happening is we are trying to load a resource string and the call eventually get to ManifestBasedResourceGroveler.GetSatelliteAssembly which is calling RuntimeAssembly.InternalGetSatelliteAssembly and passing throwOnFileNotFound=false so we shouldn’t throw any exceptions. InternalGetSatelliteAssembly will call

            RuntimeAssembly retAssembly = nLoad(an, null, this, ref stackMark,
                                IntPtr.Zero,
                                throwOnFileNotFound);

which is eventually callback the managed side Loader.AssemblyLoadContext.ResolveUsingResolvingEvent that calls AssemblyLoadContext.ResolveUsingEvent which will throw the exception while we shouldn’t do that.

            // Since attempt to resolve the assembly via Resolving event is the last option,
            // throw an exception if we do not find any assembly.
            if (assembly == null)
            {
                throw new FileNotFoundException(SR.IO_FileLoad, simpleName);
            }
                System.Private.CoreLib.dll!System.Resources.ResourceManager.GetString(string name, System.Globalization.CultureInfo culture) Line 1146          C#
               System.Private.CoreLib.dll!System.SR.InternalGetResourceString(string key) Line 128        C#
               System.Private.CoreLib.dll!System.SR.GetResourceString(string resourceKey, string defaultString) Line 39                C#
               System.Private.CoreLib.dll!System.SR.IO_FileLoad.get() Line 2643              C#
System.Private.CoreLib.dll!System.Runtime.Loader.AssemblyLoadContext.ResolveUsingEvent(System.Reflection.AssemblyName assemblyName) Line 266              C#
  System.Private.CoreLib.dll!System.Runtime.Loader.AssemblyLoadContext.ResolveUsingResolvingEvent(System.IntPtr gchManagedAssemblyLoadContext, System.Reflection.AssemblyName assemblyName) Line 191 C#
               [Native to Managed Transition] 
               [Managed to Native Transition]  
               System.Private.CoreLib.dll!System.Reflection.RuntimeAssembly.InternalGetSatelliteAssembly(string name, System.Globalization.CultureInfo culture, System.Version version, bool throwOnFileNotFound, ref System.Threading.StackCrawlMark stackMark) Line 761  C#
System.Private.CoreLib.dll!System.Resources.ManifestBasedResourceGroveler.GetSatelliteAssembly(System.Globalization.CultureInfo lookForCulture, ref System.Threading.StackCrawlMark stackMark) Line 432           C#
System.Private.CoreLib.dll!System.Resources.ManifestBasedResourceGroveler.GrovelForResourceSet(System.Globalization.CultureInfo culture, System.Collections.Generic.Dictionary<string, System.Resources.ResourceSet> localResourceSets, bool tryParents, bool createIfNotExists, ref System.Threading.StackCrawlMark stackMark) Line 82        C#
System.Private.CoreLib.dll!System.Resources.ResourceManager.InternalGetResourceSet(System.Globalization.CultureInfo requestedCulture, bool createIfNotExists, bool tryParents, ref System.Threading.StackCrawlMark stackMark) Line 692                C#
System.Private.CoreLib.dll!System.Resources.ResourceManager.InternalGetResourceSet(System.Globalization.CultureInfo culture, bool createIfNotExists, bool tryParents) Line 652               C#
               System.Private.CoreLib.dll!System.Resources.ResourceManager.GetString(string name, System.Globalization.CultureInfo culture) Line 1146          C#
               System.Private.CoreLib.dll!System.Resources.ResourceManager.GetString(string name) Line 1076              C#
System.Resources.ResourceManager.Tests.dll!System.Resources.Tests.ResourceManagerTests.TestResourceLoading() Line 27  C#

@tarekgh tarekgh assigned ghost and unassigned tarekgh Jun 16, 2017
@tarekgh
Copy link
Member

tarekgh commented Jun 16, 2017

CC @atsushikan @gkhanna79

@gkhanna79
Copy link
Member

When the Resolving event of ALC does not find any assembly resolved by the callback, it has always thrown an exception. This is not new in 2.0 but has been there since 1.0 (see https://github.com/dotnet/coreclr/blob/release/1.1.0/src/mscorlib/src/System/Runtime/Loader/AssemblyLoadContext.cs#L266 for 1.1.0 and https://github.com/dotnet/coreclr/blob/release/1.0.0/src/mscorlib/src/System/Runtime/Loader/AssemblyLoadContext.cs#L262 for 1.0.0) and is the expected behavior by design.

I suspect the problem is somewhere else.

@ghost ghost assigned tarekgh and unassigned ghost Jun 16, 2017
@ghost
Copy link

ghost commented Jun 16, 2017

cc @zamont

@gkhanna79
Copy link
Member

@tarekgh What is the calltack in Windbg corresponding to the managed/VS captured one you shared above?

@gkhanna79
Copy link
Member

@daxian-dbw .NET Core 2.0 Preview1 SDK is actually 5977 (you are using 5952, which is 25 builds old) and can be downloaded from https://github.com/dotnet/core/blob/master/release-notes/download-archives/2.0.0-preview1-download.md.

Can you confirm the issue does not repro with it?

@gkhanna79 gkhanna79 assigned gkhanna79 and unassigned tarekgh Jun 16, 2017
@tarekgh
Copy link
Member

tarekgh commented Jun 16, 2017

@gkhanna79 I am using the latest coreclr from the master branch and the problem repro there

@gkhanna79
Copy link
Member

Yes, I would have expected it there.

@gkhanna79
Copy link
Member

https://github.com/dotnet/corefx/issues/18791 was filed since LoadFrom implementation for NS 2.0 was not accounting to look for dependency assemblies in the same folder as assembly for which LoadFrom was being attempted for. This was fixed in dotnet/coreclr#11333 - however, the LoadFromResolve event handler should have returned null incase it could not load the dependency for some reason.

Since it did not, it would propagate exceptions associated with the dependent assembly loads, which is what is happening in this issue. Assembly lib.dll is loaded using LoadFrom and ResourceManager requests to load its "Lib.Resources" dependent assembly. Since that is not found (as it is not available since resources are embedded), the LoadFromResolve event handler is invoked to attempt to load the dependency. Since the dependent assembly is not available, an exception is thrown from the event handler that gets propagated out.

The fix is to return null from the event handler incase the assembly cannot be loaded for any reason.

@gkhanna79
Copy link
Member

The issue does not repro in Preview1 since Preview1 had the original LoadFrom bug https://github.com/dotnet/corefx/issues/18791 and the fix (LoadFromResolve event handler) was introduced in Preview2 only.

Also, note that this issue only affects assemblies that are loaded via LoadFrom explicitly.

@daxian-dbw
Copy link
Contributor Author

Thanks for the fix!
Are we able to use .NET Core 2.0.0-preview3 packages with the 2.0.0-preview2 .NET Core SDK (CLI)?

@gkhanna79
Copy link
Member

The fix is in the runtime, so you should be able to use preview2 sdk and ask it to target the preview3 sharedFX (or update the runtimeconfig.json for the app). However, soon enough, there will be a preview3 SDK as well.

@msftgits msftgits transferred this issue from dotnet/corefx Jan 31, 2020
@msftgits msftgits added this to the 2.0.0 milestone Jan 31, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Dec 22, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Reflection blocking Marks issues that we want to fast track in order to unblock other important work bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants