From 60478d252098ad1f3bdb98df53aaabf7d2755970 Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Thu, 17 Mar 2022 17:41:40 -0400 Subject: [PATCH] Always append the "dll" extension for native libraries loads on Windows (#66666) * Always append the "dll" extension for native libraries loads on Windows Due to case-sensitive file systems on Windows the runtime will now always append the ".dll" extension unless the name contains a trailing "." or a known loadable extensions. This is an attempt to mitigate the default Windows behavior that appends a ".DLL" which is rarely the casing used for Windows shared libraries. Co-authored-by: Jan Kotas --- src/coreclr/vm/nativelibrary.cpp | 33 +++++----- .../Loader/LibraryNameVariation.Windows.cs | 8 +-- .../NativeDependencyTests.cs | 61 ++++++++++--------- 3 files changed, 49 insertions(+), 53 deletions(-) diff --git a/src/coreclr/vm/nativelibrary.cpp b/src/coreclr/vm/nativelibrary.cpp index e050e0b1719ff..fbefbfe2dfec3 100644 --- a/src/coreclr/vm/nativelibrary.cpp +++ b/src/coreclr/vm/nativelibrary.cpp @@ -17,6 +17,8 @@ extern bool g_hostpolicy_embedded; #define PLATFORM_SHARED_LIB_SUFFIX_W PAL_SHLIB_SUFFIX_W #define PLATFORM_SHARED_LIB_PREFIX_W PAL_SHLIB_PREFIX_W #else // !TARGET_UNIX +// The default for Windows OS is ".DLL". This causes issues with case-sensitive file systems on Windows. +// We are using the lowercase version due to historical precedence and how common it is now. #define PLATFORM_SHARED_LIB_SUFFIX_W W(".dll") #define PLATFORM_SHARED_LIB_PREFIX_W W("") #endif // !TARGET_UNIX @@ -574,26 +576,19 @@ namespace int varCount = 0; - // The purpose of following code is to workaround LoadLibrary limitation: - // LoadLibrary won't append extension if filename itself contains '.'. Thus it will break the following scenario: - // [DllImport("A.B")] // The full name for file is "A.B.dll". This is common code pattern for cross-platform PInvoke - // The workaround for above scenario is to call LoadLibrary with "A.B" first, if it fails, then call LoadLibrary with "A.B.dll" - auto it = libName.Begin(); - if (!libNameIsRelativePath || - !libName.Find(it, W('.')) || - libName.EndsWith(W(".")) || - libName.EndsWithCaseInsensitive(W(".dll")) || - libName.EndsWithCaseInsensitive(W(".exe"))) + // Follow LoadLibrary rules in MSDN doc: https://docs.microsoft.com/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibrarya + // To prevent the function from appending ".DLL" to the module name, include a trailing point character (.) in the module name string + // or provide an absolute path. + libNameVariations[varCount++] = NameFmt; + + // The runtime will append the '.dll' extension if the path is relative and the name doesn't end with a "." + // or an existing known extension. This is done due to issues with case-sensitive file systems + // on Windows. The Windows loader always appends ".DLL" as opposed to the more common ".dll". + if (libNameIsRelativePath + && !libName.EndsWith(W(".")) + && !libName.EndsWithCaseInsensitive(W(".dll")) + && !libName.EndsWithCaseInsensitive(W(".exe"))) { - // Follow LoadLibrary rules in MSDN doc: https://msdn.microsoft.com/en-us/library/windows/desktop/ms684175(v=vs.85).aspx - // If the string specifies a full path, the function searches only that path for the module. - // If the string specifies a module name without a path and the file name extension is omitted, the function appends the default library extension .dll to the module name. - // To prevent the function from appending .dll to the module name, include a trailing point character (.) in the module name string. - libNameVariations[varCount++] = NameFmt; - } - else - { - libNameVariations[varCount++] = NameFmt; libNameVariations[varCount++] = NameSuffixFmt; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Loader/LibraryNameVariation.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Loader/LibraryNameVariation.Windows.cs index bbc4aacc08e49..d320b03d75996 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Loader/LibraryNameVariation.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Loader/LibraryNameVariation.Windows.cs @@ -15,10 +15,10 @@ internal static IEnumerable DetermineLibraryNameVariations yield return new LibraryNameVariation(string.Empty, string.Empty); - // Follow LoadLibrary rules if forOSLoader is true - if (isRelativePath && - !libName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) && - !libName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) + if (isRelativePath + && !libName.EndsWith(".", StringComparison.OrdinalIgnoreCase) + && !libName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) + && !libName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) { yield return new LibraryNameVariation(string.Empty, LibraryNameSuffix); } diff --git a/src/tests/Loader/AssemblyDependencyResolver/AssemblyDependencyResolverTests/NativeDependencyTests.cs b/src/tests/Loader/AssemblyDependencyResolver/AssemblyDependencyResolverTests/NativeDependencyTests.cs index b49516e33bcd8..a513a81afb9bb 100644 --- a/src/tests/Loader/AssemblyDependencyResolver/AssemblyDependencyResolverTests/NativeDependencyTests.cs +++ b/src/tests/Loader/AssemblyDependencyResolver/AssemblyDependencyResolverTests/NativeDependencyTests.cs @@ -51,21 +51,21 @@ public void TestSimpleNameAndLibPrefixAndNoSuffix() public void TestRelativeNameAndLibPrefixAndNoSuffix() { // The lib prefix is not added if the lookup is a relative path. - ValidateNativeLibraryWithRelativeLookupResolutions("lib{0}", "{0}", 0); + ValidateNativeLibraryWithRelativeLookupResolutions("lib{0}", "{0}", OS.None); } public void TestSimpleNameAndLibPrefixAndSuffix() { - ValidateNativeLibraryResolutions("lib{0}.dll", "{0}", 0); + ValidateNativeLibraryResolutions("lib{0}.dll", "{0}", OS.None); ValidateNativeLibraryResolutions("lib{0}.dylib", "{0}", OS.OSX); ValidateNativeLibraryResolutions("lib{0}.so", "{0}", OS.Linux); } public void TestNameWithSuffixAndNoPrefixAndNoSuffix() { - ValidateNativeLibraryResolutions("{0}", "{0}.dll", 0); - ValidateNativeLibraryResolutions("{0}", "{0}.dylib", 0); - ValidateNativeLibraryResolutions("{0}", "{0}.so", 0); + ValidateNativeLibraryResolutions("{0}", "{0}.dll", OS.None); + ValidateNativeLibraryResolutions("{0}", "{0}.dylib", OS.None); + ValidateNativeLibraryResolutions("{0}", "{0}.so", OS.None); } public void TestNameWithSuffixAndNoPrefixAndSuffix() @@ -78,16 +78,16 @@ public void TestNameWithSuffixAndNoPrefixAndSuffix() public void TestNameWithSuffixAndNoPrefixAndDoubleSuffix() { // Unixes add the suffix even if one is already present. - ValidateNativeLibraryResolutions("{0}.dll.dll", "{0}.dll", 0); + ValidateNativeLibraryResolutions("{0}.dll.dll", "{0}.dll", OS.None); ValidateNativeLibraryResolutions("{0}.dylib.dylib", "{0}.dylib", OS.OSX); ValidateNativeLibraryResolutions("{0}.so.so", "{0}.so", OS.Linux); } public void TestNameWithSuffixAndPrefixAndNoSuffix() { - ValidateNativeLibraryResolutions("lib{0}", "{0}.dll", 0); - ValidateNativeLibraryResolutions("lib{0}", "{0}.dylib", 0); - ValidateNativeLibraryResolutions("lib{0}", "{0}.so", 0); + ValidateNativeLibraryResolutions("lib{0}", "{0}.dll", OS.None); + ValidateNativeLibraryResolutions("lib{0}", "{0}.dylib", OS.None); + ValidateNativeLibraryResolutions("lib{0}", "{0}.so", OS.None); } public void TestNameWithSuffixAndPrefixAndSuffix() @@ -100,14 +100,14 @@ public void TestNameWithSuffixAndPrefixAndSuffix() public void TestRelativeNameWithSuffixAndPrefixAndSuffix() { // The lib prefix is not added if the lookup is a relative path - ValidateNativeLibraryWithRelativeLookupResolutions("lib{0}.dll", "{0}.dll", 0); - ValidateNativeLibraryWithRelativeLookupResolutions("lib{0}.dylib", "{0}.dylib", 0); - ValidateNativeLibraryWithRelativeLookupResolutions("lib{0}.so", "{0}.so", 0); + ValidateNativeLibraryWithRelativeLookupResolutions("lib{0}.dll", "{0}.dll", OS.None); + ValidateNativeLibraryWithRelativeLookupResolutions("lib{0}.dylib", "{0}.dylib", OS.None); + ValidateNativeLibraryWithRelativeLookupResolutions("lib{0}.so", "{0}.so", OS.None); } public void TestNameWithPrefixAndNoPrefixAndNoSuffix() { - ValidateNativeLibraryResolutions("{0}", "lib{0}", 0); + ValidateNativeLibraryResolutions("{0}", "lib{0}", OS.None); } public void TestNameWithPrefixAndPrefixAndNoSuffix() @@ -117,9 +117,9 @@ public void TestNameWithPrefixAndPrefixAndNoSuffix() public void TestNameWithPrefixAndNoPrefixAndSuffix() { - ValidateNativeLibraryResolutions("{0}.dll", "lib{0}", 0); - ValidateNativeLibraryResolutions("{0}.dylib", "lib{0}", 0); - ValidateNativeLibraryResolutions("{0}.so", "lib{0}", 0); + ValidateNativeLibraryResolutions("{0}.dll", "lib{0}", OS.None); + ValidateNativeLibraryResolutions("{0}.dylib", "lib{0}", OS.None); + ValidateNativeLibraryResolutions("{0}.so", "lib{0}", OS.None); } public void TestNameWithPrefixAndPrefixAndSuffix() @@ -136,10 +136,10 @@ public void TestWindowsAddsSuffixEvenWithOnePresent() public void TestWindowsDoesntAddSuffixWhenExectubaleIsPresent() { - ValidateNativeLibraryResolutions("{0}.dll.dll", "{0}.dll", 0); - ValidateNativeLibraryResolutions("{0}.dll.exe", "{0}.dll", 0); - ValidateNativeLibraryResolutions("{0}.exe.dll", "{0}.exe", 0); - ValidateNativeLibraryResolutions("{0}.exe.exe", "{0}.exe", 0); + ValidateNativeLibraryResolutions("{0}.dll.dll", "{0}.dll", OS.None); + ValidateNativeLibraryResolutions("{0}.dll.exe", "{0}.dll", OS.None); + ValidateNativeLibraryResolutions("{0}.exe.dll", "{0}.exe", OS.None); + ValidateNativeLibraryResolutions("{0}.exe.exe", "{0}.exe", OS.None); } private void TestLookupWithSuffixPrefersUnmodifiedSuffixOnUnixes() @@ -174,21 +174,22 @@ public void TestFullPathLookupWithMatchingFileName() public void TestFullPathLookupWithDifferentFileName() { - ValidateFullPathNativeLibraryResolutions("lib{0}", "{0}", 0); - ValidateFullPathNativeLibraryResolutions("{0}.dll", "{0}", 0); - ValidateFullPathNativeLibraryResolutions("{0}.dylib", "{0}", 0); - ValidateFullPathNativeLibraryResolutions("{0}.so", "{0}", 0); - ValidateFullPathNativeLibraryResolutions("lib{0}.dll", "{0}", 0); - ValidateFullPathNativeLibraryResolutions("lib{0}.dylib", "{0}", 0); - ValidateFullPathNativeLibraryResolutions("lib{0}.so", "{0}", 0); - ValidateFullPathNativeLibraryResolutions("lib{0}.dll", "{0}.dll", 0); - ValidateFullPathNativeLibraryResolutions("lib{0}.dylib", "{0}.dylib", 0); - ValidateFullPathNativeLibraryResolutions("lib{0}.so", "{0}.so", 0); + ValidateFullPathNativeLibraryResolutions("lib{0}", "{0}", OS.None); + ValidateFullPathNativeLibraryResolutions("{0}.dll", "{0}", OS.None); + ValidateFullPathNativeLibraryResolutions("{0}.dylib", "{0}", OS.None); + ValidateFullPathNativeLibraryResolutions("{0}.so", "{0}", OS.None); + ValidateFullPathNativeLibraryResolutions("lib{0}.dll", "{0}", OS.None); + ValidateFullPathNativeLibraryResolutions("lib{0}.dylib", "{0}", OS.None); + ValidateFullPathNativeLibraryResolutions("lib{0}.so", "{0}", OS.None); + ValidateFullPathNativeLibraryResolutions("lib{0}.dll", "{0}.dll", OS.None); + ValidateFullPathNativeLibraryResolutions("lib{0}.dylib", "{0}.dylib", OS.None); + ValidateFullPathNativeLibraryResolutions("lib{0}.so", "{0}.so", OS.None); } [Flags] private enum OS { + None = 0, Windows = 0x1, OSX = 0x2, Linux = 0x4