Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit a65e2b9

Browse files
authored
Fix Satellite Assembly loading (#24191)
* Fix Satellite Assembly loading 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 * Call ResolveSatelliteAssembly from native Only call ResolveSatelliteAssembly from native when resolving a satellite assembly * PR Feedback Minimize string creation Remove unnecessary if null checks Eliminate corner cases by only allowing one case insensitive matching directory. * ResolveSatelliteAssembly should ... ResolveSatelliteAssembly should always be called on the ALC which loaded parentAssembly Simplify code. Add Debug.Assert * Remove case insensitive culture search * PR Feedback * Fix parentAssembly logic * Fixes from initial testing * Add probe for lower case culture name * PR feedback
1 parent 78f53be commit a65e2b9

File tree

7 files changed

+95
-13
lines changed

7 files changed

+95
-13
lines changed

src/System.Private.CoreLib/shared/System/Runtime/Loader/AssemblyLoadContext.cs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ public event Func<Assembly, string, IntPtr> ResolvingUnmanagedDll
187187
//
188188
// Inputs: The AssemblyLoadContext and AssemblyName to be loaded
189189
// Returns: The Loaded assembly object.
190-
public event Func<AssemblyLoadContext, AssemblyName, Assembly> Resolving
190+
public event Func<AssemblyLoadContext, AssemblyName, Assembly?> Resolving
191191
{
192192
add
193193
{
@@ -555,6 +555,43 @@ public void Dispose()
555555
}
556556
}
557557
}
558+
559+
private Assembly? ResolveSatelliteAssembly(AssemblyName assemblyName)
560+
{
561+
// Called by native runtime when CultureName is not empty
562+
Debug.Assert(assemblyName.CultureName?.Length > 0);
563+
564+
string satelliteSuffix = ".resources";
565+
566+
if (assemblyName.Name == null || !assemblyName.Name.EndsWith(satelliteSuffix, StringComparison.Ordinal))
567+
return null;
568+
569+
string parentAssemblyName = assemblyName.Name.Substring(0, assemblyName.Name.Length - satelliteSuffix.Length);
570+
571+
Assembly parentAssembly = LoadFromAssemblyName(new AssemblyName(parentAssemblyName));
572+
573+
AssemblyLoadContext parentALC = GetLoadContext(parentAssembly)!;
574+
575+
string parentDirectory = Path.GetDirectoryName(parentAssembly.Location)!;
576+
577+
string assemblyPath = Path.Combine(parentDirectory, assemblyName.CultureName!, $"{assemblyName.Name}.dll");
578+
579+
if (Internal.IO.File.InternalExists(assemblyPath))
580+
{
581+
return parentALC.LoadFromAssemblyPath(assemblyPath);
582+
}
583+
else if (Path.IsCaseSensitive)
584+
{
585+
assemblyPath = Path.Combine(parentDirectory, assemblyName.CultureName!.ToLowerInvariant(), $"{assemblyName.Name}.dll");
586+
587+
if (Internal.IO.File.InternalExists(assemblyPath))
588+
{
589+
return parentALC.LoadFromAssemblyPath(assemblyPath);
590+
}
591+
}
592+
593+
return null;
594+
}
558595
}
559596

560597
internal sealed class DefaultAssemblyLoadContext : AssemblyLoadContext

src/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.CoreCLR.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,24 @@ internal Assembly LoadFromInMemoryModule(IntPtr moduleHandle)
9393

9494
// This method is invoked by the VM to resolve an assembly reference using the Resolving event
9595
// after trying assembly resolution via Load override and TPA load context without success.
96-
private static Assembly ResolveUsingResolvingEvent(IntPtr gchManagedAssemblyLoadContext, AssemblyName assemblyName)
96+
private static Assembly? ResolveUsingResolvingEvent(IntPtr gchManagedAssemblyLoadContext, AssemblyName assemblyName)
9797
{
9898
AssemblyLoadContext context = (AssemblyLoadContext)(GCHandle.FromIntPtr(gchManagedAssemblyLoadContext).Target)!;
9999

100100
// Invoke the AssemblyResolve event callbacks if wired up
101101
return context.ResolveUsingEvent(assemblyName);
102102
}
103103

104+
// This method is invoked by the VM to resolve a satellite assembly reference
105+
// after trying assembly resolution via Load override without success.
106+
private static Assembly? ResolveSatelliteAssembly(IntPtr gchManagedAssemblyLoadContext, AssemblyName assemblyName)
107+
{
108+
AssemblyLoadContext context = (AssemblyLoadContext)(GCHandle.FromIntPtr(gchManagedAssemblyLoadContext).Target)!;
109+
110+
// Invoke the ResolveSatelliteAssembly method
111+
return context.ResolveSatelliteAssembly(assemblyName);
112+
}
113+
104114
private Assembly? GetFirstResolvedAssembly(AssemblyName assemblyName)
105115
{
106116
Assembly? resolvedAssembly = null;

src/binder/clrprivbinderassemblyloadcontext.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,10 @@ HRESULT CLRPrivBinderAssemblyLoadContext::BindAssemblyByName(IAssemblyName *
6565
//
6666
// 1) Lookup the assembly within the LoadContext itself. If assembly is found, use it.
6767
// 2) Invoke the LoadContext's Load method implementation. If assembly is found, use it.
68-
// 3) Lookup the assembly within TPABinder. If assembly is found, use it.
69-
// 4) Invoke the LoadContext's Resolving event. If assembly is found, use it.
70-
// 5) Raise exception.
68+
// 3) Lookup the assembly within TPABinder (except for satellite requests). If assembly is found, use it.
69+
// 4) Invoke the LoadContext's ResolveSatelliteAssembly method (for satellite requests). If assembly is found, use it.
70+
// 5) Invoke the LoadContext's Resolving event. If assembly is found, use it.
71+
// 6) Raise exception.
7172
//
7273
// This approach enables a LoadContext to override assemblies that have been loaded in TPA context by loading
7374
// a different (or even the same!) version.

src/vm/appdomain.cpp

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6792,6 +6792,8 @@ HRESULT RuntimeInvokeHostAssemblyResolver(INT_PTR pManagedAssemblyLoadContextToB
67926792
// Initialize the AssemblyName object from the AssemblySpec
67936793
spec.AssemblyNameInit(&_gcRefs.oRefAssemblyName, NULL);
67946794

6795+
bool isSatelliteAssemblyRequest = !spec.IsNeutralCulture();
6796+
67956797
if (!fInvokedForTPABinder)
67966798
{
67976799
// Step 2 (of CLRPrivBinderAssemblyLoadContext::BindUsingAssemblyName) - Invoke Load method
@@ -6814,9 +6816,9 @@ HRESULT RuntimeInvokeHostAssemblyResolver(INT_PTR pManagedAssemblyLoadContextToB
68146816
{
68156817
fResolvedAssembly = true;
68166818
}
6817-
6819+
68186820
// Step 3 (of CLRPrivBinderAssemblyLoadContext::BindUsingAssemblyName)
6819-
if (!fResolvedAssembly)
6821+
if (!fResolvedAssembly && !isSatelliteAssemblyRequest)
68206822
{
68216823
// If we could not resolve the assembly using Load method, then attempt fallback with TPA Binder.
68226824
// Since TPA binder cannot fallback to itself, this fallback does not happen for binds within TPA binder.
@@ -6833,16 +6835,41 @@ HRESULT RuntimeInvokeHostAssemblyResolver(INT_PTR pManagedAssemblyLoadContextToB
68336835
}
68346836
}
68356837
}
6836-
6837-
if (!fResolvedAssembly)
6838+
6839+
if (!fResolvedAssembly && isSatelliteAssemblyRequest)
68386840
{
68396841
// Step 4 (of CLRPrivBinderAssemblyLoadContext::BindUsingAssemblyName)
68406842
//
6843+
// Attempt to resolve it using the ResolveSatelliteAssembly method.
6844+
// Finally, setup arguments for invocation
6845+
BinderMethodID idHAR_ResolveSatelitteAssembly = METHOD__ASSEMBLYLOADCONTEXT__RESOLVESATELLITEASSEMBLY;
6846+
MethodDescCallSite methResolveSatelitteAssembly(idHAR_ResolveSatelitteAssembly);
6847+
6848+
// Setup the arguments for the call
6849+
ARG_SLOT args[2] =
6850+
{
6851+
PtrToArgSlot(pManagedAssemblyLoadContextToBindWithin), // IntPtr for managed assembly load context instance
6852+
ObjToArgSlot(_gcRefs.oRefAssemblyName), // AssemblyName instance
6853+
};
6854+
6855+
// Make the call
6856+
_gcRefs.oRefLoadedAssembly = (ASSEMBLYREF) methResolveSatelitteAssembly.Call_RetOBJECTREF(args);
6857+
if (_gcRefs.oRefLoadedAssembly != NULL)
6858+
{
6859+
// Set the flag indicating we found the assembly
6860+
fResolvedAssembly = true;
6861+
}
6862+
}
6863+
6864+
if (!fResolvedAssembly)
6865+
{
6866+
// Step 5 (of CLRPrivBinderAssemblyLoadContext::BindUsingAssemblyName)
6867+
//
68416868
// If we couldnt resolve the assembly using TPA LoadContext as well, then
68426869
// attempt to resolve it using the Resolving event.
68436870
// Finally, setup arguments for invocation
68446871
BinderMethodID idHAR_ResolveUsingEvent = METHOD__ASSEMBLYLOADCONTEXT__RESOLVEUSINGEVENT;
6845-
MethodDescCallSite methLoadAssembly(idHAR_ResolveUsingEvent);
6872+
MethodDescCallSite methResolveUsingEvent(idHAR_ResolveUsingEvent);
68466873

68476874
// Setup the arguments for the call
68486875
ARG_SLOT args[2] =
@@ -6852,7 +6879,7 @@ HRESULT RuntimeInvokeHostAssemblyResolver(INT_PTR pManagedAssemblyLoadContextToB
68526879
};
68536880

68546881
// Make the call
6855-
_gcRefs.oRefLoadedAssembly = (ASSEMBLYREF) methLoadAssembly.Call_RetOBJECTREF(args);
6882+
_gcRefs.oRefLoadedAssembly = (ASSEMBLYREF) methResolveUsingEvent.Call_RetOBJECTREF(args);
68566883
if (_gcRefs.oRefLoadedAssembly != NULL)
68576884
{
68586885
// Set the flag indicating we found the assembly

src/vm/baseassemblyspec.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ class BaseAssemblySpec
9898
void SetCodeBase(LPCWSTR szCodeBase);
9999

100100
VOID SetCulture(LPCSTR szCulture);
101+
bool IsNeutralCulture();
101102

102103
VOID ConvertPublicKeyToToken();
103104

src/vm/baseassemblyspec.inl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,11 @@ inline void BaseAssemblySpec::SetCulture(LPCSTR szCulture)
582582
m_context.szLocale=szCulture;
583583
}
584584

585+
inline bool BaseAssemblySpec::IsNeutralCulture()
586+
{
587+
return strcmp(m_context.szLocale,"")==0;
588+
}
589+
585590
inline void BaseAssemblySpec::SetContext(ASSEMBLYMETADATA* assemblyData)
586591
{
587592
LIMITED_METHOD_CONTRACT;

src/vm/mscorlib.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -899,9 +899,10 @@ DEFINE_FIELD_U(_state, AssemblyLoadContextBaseObject, _stat
899899
DEFINE_FIELD_U(_isCollectible, AssemblyLoadContextBaseObject, _isCollectible)
900900
DEFINE_CLASS(ASSEMBLYLOADCONTEXT, Loader, AssemblyLoadContext)
901901
DEFINE_METHOD(ASSEMBLYLOADCONTEXT, RESOLVE, Resolve, SM_IntPtr_AssemblyName_RetAssemblyBase)
902-
DEFINE_METHOD(ASSEMBLYLOADCONTEXT, RESOLVEUNMANAGEDDLL, ResolveUnmanagedDll, SM_Str_IntPtr_RetIntPtr)
902+
DEFINE_METHOD(ASSEMBLYLOADCONTEXT, RESOLVEUNMANAGEDDLL, ResolveUnmanagedDll, SM_Str_IntPtr_RetIntPtr)
903903
DEFINE_METHOD(ASSEMBLYLOADCONTEXT, RESOLVEUNMANAGEDDLLUSINGEVENT, ResolveUnmanagedDllUsingEvent, SM_Str_AssemblyBase_IntPtr_RetIntPtr)
904-
DEFINE_METHOD(ASSEMBLYLOADCONTEXT, RESOLVEUSINGEVENT, ResolveUsingResolvingEvent, SM_IntPtr_AssemblyName_RetAssemblyBase)
904+
DEFINE_METHOD(ASSEMBLYLOADCONTEXT, RESOLVEUSINGEVENT, ResolveUsingResolvingEvent, SM_IntPtr_AssemblyName_RetAssemblyBase)
905+
DEFINE_METHOD(ASSEMBLYLOADCONTEXT, RESOLVESATELLITEASSEMBLY, ResolveSatelliteAssembly, SM_IntPtr_AssemblyName_RetAssemblyBase)
905906
DEFINE_FIELD(ASSEMBLYLOADCONTEXT, ASSEMBLY_LOAD, AssemblyLoad)
906907
DEFINE_METHOD(ASSEMBLYLOADCONTEXT, ON_ASSEMBLY_LOAD, OnAssemblyLoad, SM_Assembly_RetVoid)
907908
DEFINE_METHOD(ASSEMBLYLOADCONTEXT, ON_RESOURCE_RESOLVE, OnResourceResolve, SM_Assembly_Str_RetAssembly)

0 commit comments

Comments
 (0)