Skip to content

Commit

Permalink
Fix Satellite Assembly loading
Browse files Browse the repository at this point in the history
When loading satellite assemblies, we should probe next to the parent
assembly and load into the same AssemblyLoadContext as the parent
assembly.

Disable fallback probing for satellite assemblies.

Add AssemblyLoadContext.Resolving handler to probe for satellite
assemblies next to parent

Fixes #20979
  • Loading branch information
sdmaclea committed Apr 24, 2019
1 parent 3f21a0f commit c4b99ef
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1149,6 +1149,7 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\HijriCalendar.Win32.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\JapaneseCalendar.Win32.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\FileStream.Win32.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Loader\AssemblyLoadContext.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\TimeZoneInfo.Win32.cs" />
</ItemGroup>
<ItemGroup Condition="$(TargetsWindows) and '$(EnableWinRT)' == 'true'">
Expand Down Expand Up @@ -1266,6 +1267,7 @@
<Compile Include="$(MSBuildThisFileDirectory)System\IO\PersistedFiles.Names.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\PasteArguments.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\MemoryFailPoint.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Loader\AssemblyLoadContext.Unix.cs" Condition="'$(TargetsCoreRT)' != 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\Marshal.Unix.cs" Condition="'$(TargetsCoreRT)' != 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Security\SecureString.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\Thread.Unix.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable enable
using System.Buffers;
using System.IO;
using System.Reflection;

namespace System.Runtime.Loader
{
public partial class AssemblyLoadContext
{
// Find satellite path using case insensitive culture name
internal static unsafe string? FindCaseInsensitiveSatellitePath(string parentDirectory, string cultureName, string assembly)
{
int bufferSize = Interop.Sys.GetReadDirRBufferSize();
byte[]? dirBuffer = null;
try
{
dirBuffer = ArrayPool<byte>.Shared.Rent(bufferSize);

fixed (byte* dirBufferPtr = dirBuffer)
{
IntPtr dirHandle = Interop.Sys.OpenDir(parentDirectory);
if (dirHandle != IntPtr.Zero)
{
try
{
Interop.Sys.DirectoryEntry dirent;
while (Interop.Sys.ReadDirR(dirHandle, dirBufferPtr, bufferSize, out dirent) == 0)
{
if (dirent.InodeType != Interop.Sys.NodeType.DT_DIR)
continue;

Span<char> nameBuffer = stackalloc char[Interop.Sys.DirectoryEntry.NameBufferSize];
ReadOnlySpan<char> entryName = dirent.GetName(nameBuffer);

if (cultureName.Length != entryName.Length)
continue;

string entryNameString = entryName.ToString();

if (!cultureName.Equals(entryNameString, StringComparison.InvariantCultureIgnoreCase))
continue;

string assemblyPath = $"{parentDirectory}/{entryNameString}/{assembly}.dll";

if (Internal.IO.File.InternalExists(assemblyPath))
return assemblyPath;
}
}
finally
{
if (dirHandle != IntPtr.Zero)
Interop.Sys.CloseDir(dirHandle);
}
}
}
}
finally
{
if (dirBuffer != null)
ArrayPool<byte>.Shared.Return(dirBuffer);
}
return null;
}

static private Assembly? ResolveSatelliteAssembly(AssemblyLoadContext alc, AssemblyName assemblyName)
{
string? cultureName = assemblyName.CultureName;

if (cultureName == null || cultureName.Length == 0)
return null;

if (assemblyName.Name == null)
return null;

AssemblyName parentAssemblyName = new AssemblyName(assemblyName.Name);

Assembly? parentAssembly = alc.LoadFromAssemblyName(parentAssemblyName);

if (parentAssembly == null)
return null;

AssemblyLoadContext? parentAlc = GetLoadContext(parentAssembly);

if (parentAlc == null)
return null;

string parentDirectory = Path.GetDirectoryName(parentAssembly.Location)!;

string assemblyPath = $"{parentDirectory}/{cultureName}/{assemblyName.Name}.dll";
if (Internal.IO.File.InternalExists(assemblyPath))
{
Assembly satelliteAssembly = parentAlc.LoadFromAssemblyPath(assemblyPath);

if (satelliteAssembly != null)
return satelliteAssembly;
}
else if (Path.IsCaseSensitive)
{
string? caseInsensitiveAssemblyPath = FindCaseInsensitiveSatellitePath(parentDirectory, cultureName, assemblyName.Name);

if (caseInsensitiveAssemblyPath != null)
{
Assembly satelliteAssembly = parentAlc.LoadFromAssemblyPath(caseInsensitiveAssemblyPath);

return satelliteAssembly;
}
}
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable enable
using System.IO;
using System.Reflection;

namespace System.Runtime.Loader
{
public partial class AssemblyLoadContext
{
static private Assembly? ResolveSatelliteAssembly(AssemblyLoadContext alc, AssemblyName assemblyName)
{
string? cultureName = assemblyName.CultureName;

if (cultureName == null || cultureName.Length == 0)
return null;

if (assemblyName.Name == null)
return null;

AssemblyName parentAssemblyName = new AssemblyName(assemblyName.Name);

Assembly? parentAssembly = alc.LoadFromAssemblyName(parentAssemblyName);

if (parentAssembly == null)
return null;

AssemblyLoadContext? parentAlc = GetLoadContext(parentAssembly);

if (parentAlc == null)
return null;

string parentDirectory = Path.GetDirectoryName(parentAssembly.Location)!;

string assemblyPath = Path.Combine(parentDirectory, dir, $"{assemblyName.Name}.dll");
if (Internal.IO.File.InternalExists(assemblyPath))
{
Assembly satelliteAssembly = parentAlc.LoadFromAssemblyPath(assemblyPath);

if (satelliteAssembly != null)
return satelliteAssembly;
}

return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ private protected AssemblyLoadContext(bool representsTPALoadContext, bool isColl
_id = s_nextId++;
s_allContexts.Add(_id, new WeakReference<AssemblyLoadContext>(this, true));
}

Resolving += ResolveSatelliteAssembly;
}

~AssemblyLoadContext()
Expand Down Expand Up @@ -187,7 +189,7 @@ public event Func<Assembly, string, IntPtr> ResolvingUnmanagedDll
//
// Inputs: The AssemblyLoadContext and AssemblyName to be loaded
// Returns: The Loaded assembly object.
public event Func<AssemblyLoadContext, AssemblyName, Assembly> Resolving
public event Func<AssemblyLoadContext, AssemblyName, Assembly?> Resolving
{
add
{
Expand Down
6 changes: 4 additions & 2 deletions src/vm/appdomain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6909,9 +6909,11 @@ HRESULT RuntimeInvokeHostAssemblyResolver(INT_PTR pManagedAssemblyLoadContextToB
{
fResolvedAssembly = true;
}


bool isSatelliteAssemblyRequest = (_gcRefs.oRefAssemblyName->GetCultureInfo() != NULL);

// Step 3 (of CLRPrivBinderAssemblyLoadContext::BindUsingAssemblyName)
if (!fResolvedAssembly)
if (!fResolvedAssembly && !isSatelliteAssemblyRequest)
{
// If we could not resolve the assembly using Load method, then attempt fallback with TPA Binder.
// Since TPA binder cannot fallback to itself, this fallback does not happen for binds within TPA binder.
Expand Down

0 comments on commit c4b99ef

Please sign in to comment.