From 8cf193fd51a9aedbb6c8406d2fbf9e10ae0aed33 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 25 Mar 2026 20:54:00 +0000
Subject: [PATCH 1/6] Initial plan
From 208059a4d44ad332e85dc2fbbd4b0051f4a6cce3 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 25 Mar 2026 21:00:11 +0000
Subject: [PATCH 2/6] Fix R2R TypeMapAssemblyTargets emitting empty entries for
unresolved assemblies
When CrossGen2 encounters a TypeMapAssemblyTarget pointing to an
assembly that fails to resolve (e.g. "DoesNotExist"), it catches the
exception and sets exception stubs, but AddTargetModule is never
called, leaving TargetModules empty.
Previously, TypeMapAssemblyTargetsNode still emitted an entry with
count=0 for these groups. At runtime, HasTypeMapAssemblyTargets would
find this entry, return true with count=0, setting hasPrecachedTargets
to true. This prevented the runtime from falling back to attribute
processing, which is where the FileNotFoundException would be thrown.
The fix skips emitting TypeMapAssemblyTargets entries for groups with
no resolved target modules. This makes HasTypeMapAssemblyTargets
return false for these groups, causing the runtime to correctly fall
back to attribute processing and throw FileNotFoundException.
Fixes dotnet#125941
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/f1301246-c321-4665-a75e-28efe25a1e5e
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
---
.../Compiler/TypeMapAssemblyTargetsNode.cs | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/TypeMapAssemblyTargetsNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/TypeMapAssemblyTargetsNode.cs
index a3bd9dddb54c54..805b0d52a2bca3 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/TypeMapAssemblyTargetsNode.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/TypeMapAssemblyTargetsNode.cs
@@ -32,6 +32,11 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact
DependencyList dependencies = [];
foreach (var map in _assemblyTypeMaps.Maps)
{
+ // Skip groups with no resolved targets (e.g. when the target assembly failed to resolve).
+ // The runtime will fall back to attribute processing for these groups.
+ if (map.Value.TargetModules.Count == 0)
+ continue;
+
var groupType = map.Key;
dependencies.Add(new DependencyListEntry(_importReferenceProvider.GetImportToType(groupType), "Type Map Assembly Target"));
foreach (var targetModule in map.Value.TargetModules)
@@ -59,6 +64,11 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
foreach (var map in _assemblyTypeMaps.Maps)
{
+ // Skip groups with no resolved targets (e.g. when the target assembly failed to resolve).
+ // The runtime will fall back to attribute processing for these groups.
+ if (map.Value.TargetModules.Count == 0)
+ continue;
+
var groupType = map.Key;
Vertex groupTypeVertex = _importReferenceProvider.EncodeReferenceToType(writer, groupType);
VertexSequence modules = new();
From 4215928619ed27c1f767cf1cb3056d2a1a632648 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 26 Mar 2026 00:25:23 +0000
Subject: [PATCH 3/6] Fix runtime to re-process assembly target attributes when
external/proxy maps aren't pre-cached
When CrossGen2 fails to resolve an assembly target (e.g. "DoesNotExist"),
it emits a TypeMapAssemblyTargets entry with count=0 but marks the
external/proxy type maps as invalid (state=0). Previously the runtime
only re-processed assembly target attributes when hasPrecachedTargets
was false.
With this change, the runtime also re-processes assembly target
attributes when the external or proxy type maps weren't pre-cached.
This is the more robust fix (Option 1 from the analysis) that also
handles the mixed case where some assembly targets resolve and some
don't. The AssemblyTargetProcessor already deduplicates, so
re-processing is safe.
Co-authored-by: mangod9 <61718172+mangod9@users.noreply.github.com>
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/35d99139-2cbe-4b13-a96f-f26d5e5bd8d2
---
src/coreclr/vm/assemblynative.cpp | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/src/coreclr/vm/assemblynative.cpp b/src/coreclr/vm/assemblynative.cpp
index ce938e9549ff38..32251dd831e725 100644
--- a/src/coreclr/vm/assemblynative.cpp
+++ b/src/coreclr/vm/assemblynative.cpp
@@ -1773,9 +1773,17 @@ extern "C" void QCALLTYPE TypeMapLazyDictionary_ProcessAttributes(
(newProxyTypeEntry != nullptr && !hasPrecachedProxy) ||
!hasPrecachedTargets)
{
- // Only fall back to attribute parsing for the assembly targets if they were
- // not found in the pre-cached R2R section.
- if (!hasPrecachedTargets)
+ // Fall back to attribute parsing for the assembly targets if they were
+ // not found in the pre-cached R2R section, or if the external/proxy type
+ // maps were not pre-cached. When CrossGen2 fails to resolve an assembly
+ // target, it emits an assembly targets entry with count=0 but marks the
+ // external/proxy maps as invalid (state=0). Re-processing the assembly
+ // target attributes in that case ensures the runtime correctly loads (and
+ // throws for) unresolvable assemblies. The AssemblyTargetProcessor already
+ // deduplicates, so re-processing is safe.
+ if (!hasPrecachedTargets
+ || (newExternalTypeEntry != nullptr && !hasPrecachedExternal)
+ || (newProxyTypeEntry != nullptr && !hasPrecachedProxy))
{
ProcessTypeMapAttribute(
TypeMapAssemblyTargetAttributeName,
From bd14f210ef8a97c48c76065faf4bf075a2aabec2 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 26 Mar 2026 08:16:46 +0000
Subject: [PATCH 4/6] Remove redundant inner condition matching outer if-block
in TypeMap fallback
Co-authored-by: mangod9 <61718172+mangod9@users.noreply.github.com>
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/e777151e-cbef-474b-8fdd-8c7832a8b797
---
src/coreclr/vm/assemblynative.cpp | 15 +++++----------
1 file changed, 5 insertions(+), 10 deletions(-)
diff --git a/src/coreclr/vm/assemblynative.cpp b/src/coreclr/vm/assemblynative.cpp
index 32251dd831e725..a4d71202470ed7 100644
--- a/src/coreclr/vm/assemblynative.cpp
+++ b/src/coreclr/vm/assemblynative.cpp
@@ -1781,16 +1781,11 @@ extern "C" void QCALLTYPE TypeMapLazyDictionary_ProcessAttributes(
// target attributes in that case ensures the runtime correctly loads (and
// throws for) unresolvable assemblies. The AssemblyTargetProcessor already
// deduplicates, so re-processing is safe.
- if (!hasPrecachedTargets
- || (newExternalTypeEntry != nullptr && !hasPrecachedExternal)
- || (newProxyTypeEntry != nullptr && !hasPrecachedProxy))
- {
- ProcessTypeMapAttribute(
- TypeMapAssemblyTargetAttributeName,
- assemblies,
- groupTypeMT,
- currAssembly);
- }
+ ProcessTypeMapAttribute(
+ TypeMapAssemblyTargetAttributeName,
+ assemblies,
+ groupTypeMT,
+ currAssembly);
// We will only process the specific type maps if we have a callback to process
// the entry and the precached map was not calculated for this module.
From 7953285ad6b338222ade5b645a3c265f13f0c28a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 30 Mar 2026 19:52:49 +0000
Subject: [PATCH 5/6] Narrow CrossGen2 skip to only groups with failed assembly
target attributes, add test
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/06cdbbd9-f8e1-4b19-9254-cb79cb2e2102
Co-authored-by: mangod9 <61718172+mangod9@users.noreply.github.com>
---
.../tools/Common/Compiler/TypeMapMetadata.cs | 16 +++++++++++++++
.../Compiler/TypeMapAssemblyTargetsNode.cs | 14 +++++++------
src/tests/Interop/TypeMap/TypeMapApp.cs | 20 +++++++++++++++++++
3 files changed, 44 insertions(+), 6 deletions(-)
diff --git a/src/coreclr/tools/Common/Compiler/TypeMapMetadata.cs b/src/coreclr/tools/Common/Compiler/TypeMapMetadata.cs
index c0a98386343a21..d966c1d672032d 100644
--- a/src/coreclr/tools/Common/Compiler/TypeMapMetadata.cs
+++ b/src/coreclr/tools/Common/Compiler/TypeMapMetadata.cs
@@ -107,6 +107,14 @@ public Map(TypeDesc typeMapGroup)
public TypeDesc TypeMapGroup { get; }
+ ///
+ /// Indicates whether any TypeMapAssemblyTarget attributes were processed for this group,
+ /// regardless of whether the target assembly was successfully resolved. When true and
+ /// is empty, it means all target attributes failed to resolve
+ /// and the runtime should fall back to attribute processing.
+ ///
+ public bool HasAssemblyTargetAttributes { get; set; }
+
public void AddAssociatedTypeMapEntry(TypeDesc type, TypeDesc associatedType)
{
if (!_associatedTypeMap.TryAdd(type, associatedType))
@@ -205,6 +213,7 @@ public void MergePendingMap(ModuleDesc stubModule, Map pendingMap)
}
_targetModules.AddRange(pendingMap._targetModules);
+ HasAssemblyTargetAttributes |= pendingMap.HasAssemblyTargetAttributes;
}
public void AddTargetModule(ModuleDesc targetModule)
@@ -383,6 +392,11 @@ public static TypeMapMetadata CreateFromAssembly(EcmaAssembly assembly, ModuleDe
typeMapStates[typeMapGroup] = value;
}
+ if (attrKind is TypeMapAttributeKind.TypeMapAssemblyTarget)
+ {
+ value.HasAssemblyTargetAttributes = true;
+ }
+
if (attrKind is TypeMapAttributeKind.TypeMapAssemblyTarget or TypeMapAttributeKind.TypeMap)
{
value.SetExternalTypeMapException(throwHelperEmitModule, ex);
@@ -404,6 +418,8 @@ public static TypeMapMetadata CreateFromAssembly(EcmaAssembly assembly, ModuleDe
void ProcessTypeMapAssemblyTargetAttribute(CustomAttributeValue attrValue, Map typeMapState)
{
+ typeMapState.HasAssemblyTargetAttributes = true;
+
if (attrValue.FixedArguments is not [{ Value: string assemblyName }])
{
ThrowHelper.ThrowBadImageFormatException();
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/TypeMapAssemblyTargetsNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/TypeMapAssemblyTargetsNode.cs
index 805b0d52a2bca3..0806d702d7929c 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/TypeMapAssemblyTargetsNode.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/TypeMapAssemblyTargetsNode.cs
@@ -32,9 +32,12 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact
DependencyList dependencies = [];
foreach (var map in _assemblyTypeMaps.Maps)
{
- // Skip groups with no resolved targets (e.g. when the target assembly failed to resolve).
- // The runtime will fall back to attribute processing for these groups.
- if (map.Value.TargetModules.Count == 0)
+ // Skip groups where assembly target attributes were present but all failed to resolve
+ // (e.g. when the target assembly name doesn't exist). The runtime will fall back to
+ // attribute processing for these groups. Groups with no assembly target attributes
+ // at all should still emit an entry so the runtime knows they are precached and
+ // avoids unnecessary fallback to attribute scanning.
+ if (map.Value.TargetModules.Count == 0 && map.Value.HasAssemblyTargetAttributes)
continue;
var groupType = map.Key;
@@ -64,9 +67,8 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
foreach (var map in _assemblyTypeMaps.Maps)
{
- // Skip groups with no resolved targets (e.g. when the target assembly failed to resolve).
- // The runtime will fall back to attribute processing for these groups.
- if (map.Value.TargetModules.Count == 0)
+ // Skip groups where assembly target attributes were present but all failed to resolve.
+ if (map.Value.TargetModules.Count == 0 && map.Value.HasAssemblyTargetAttributes)
continue;
var groupType = map.Key;
diff --git a/src/tests/Interop/TypeMap/TypeMapApp.cs b/src/tests/Interop/TypeMap/TypeMapApp.cs
index 02259e4738eb54..c757732df41610 100644
--- a/src/tests/Interop/TypeMap/TypeMapApp.cs
+++ b/src/tests/Interop/TypeMap/TypeMapApp.cs
@@ -265,6 +265,26 @@ public static void Validate_MissingAssemblyTarget()
Assert.Throws(() => TypeMapping.GetOrCreateProxyTypeMapping());
}
+ [Fact]
+ public static void Validate_MissingAssemblyTarget_DoesNotAffectGroupsWithoutTargets()
+ {
+ // Validates that groups without TypeMapAssemblyTarget attributes (like TypicalUseCase)
+ // still work correctly alongside groups with failing targets (like UnknownAssemblyReference).
+ // In R2R, groups without assembly targets must have their precached entry emitted so the
+ // runtime doesn't unnecessarily fall back to attribute scanning.
+ Console.WriteLine(nameof(Validate_MissingAssemblyTarget_DoesNotAffectGroupsWithoutTargets));
+
+ Assert.Throws(() => TypeMapping.GetOrCreateExternalTypeMapping());
+
+ IReadOnlyDictionary externalMap = TypeMapping.GetOrCreateExternalTypeMapping();
+ Assert.Equal(typeof(C1), externalMap["1"]);
+ Assert.Equal(typeof(S1), externalMap["2"]);
+
+ IReadOnlyDictionary proxyMap = TypeMapping.GetOrCreateProxyTypeMapping();
+ Assert.Equal(typeof(C1), proxyMap[new C1().GetType()]);
+ Assert.Equal(typeof(S1), proxyMap[((object)default(S1)).GetType()]);
+ }
+
[Fact]
public static void Validate_EmptyOrInvalidMappings()
{
From 371ad9f94b29cf2198c01a3fa8c0a5890815166c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 31 Mar 2026 03:50:58 +0000
Subject: [PATCH 6/6] Enhance test to validate TypeMap entries exist in R2R
image
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/57f5b8fc-faa2-4d51-869f-8c9999cbd234
Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
---
src/tests/Interop/TypeMap/TypeMapApp.cs | 28 +++++++++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/src/tests/Interop/TypeMap/TypeMapApp.cs b/src/tests/Interop/TypeMap/TypeMapApp.cs
index c757732df41610..9c07412d5e3282 100644
--- a/src/tests/Interop/TypeMap/TypeMapApp.cs
+++ b/src/tests/Interop/TypeMap/TypeMapApp.cs
@@ -283,6 +283,34 @@ public static void Validate_MissingAssemblyTarget_DoesNotAffectGroupsWithoutTarg
IReadOnlyDictionary proxyMap = TypeMapping.GetOrCreateProxyTypeMapping();
Assert.Equal(typeof(C1), proxyMap[new C1().GetType()]);
Assert.Equal(typeof(S1), proxyMap[((object)default(S1)).GetType()]);
+
+ // When running in R2R mode, verify the R2R image contains TypeMap sections.
+ // This confirms CrossGen2 correctly emitted entries for groups without
+ // TypeMapAssemblyTarget attributes alongside groups with failed targets.
+ string assemblyLocation = typeof(TypeMap).Assembly.Location;
+ if (!string.IsNullOrEmpty(assemblyLocation))
+ {
+ string r2rDumpFile = assemblyLocation + ".r2rdump";
+ if (File.Exists(r2rDumpFile))
+ {
+ string[] lines = File.ReadAllLines(r2rDumpFile);
+ bool hasExternalTypeMaps = false;
+ bool hasProxyTypeMaps = false;
+ bool hasTypeMapAssemblyTargets = false;
+ foreach (string line in lines)
+ {
+ if (line.Contains("ExternalTypeMaps", StringComparison.Ordinal))
+ hasExternalTypeMaps = true;
+ if (line.Contains("ProxyTypeMaps", StringComparison.Ordinal))
+ hasProxyTypeMaps = true;
+ if (line.Contains("TypeMapAssemblyTargets", StringComparison.Ordinal))
+ hasTypeMapAssemblyTargets = true;
+ }
+ Assert.True(hasExternalTypeMaps, "R2R image should contain ExternalTypeMaps section");
+ Assert.True(hasProxyTypeMaps, "R2R image should contain ProxyTypeMaps section");
+ Assert.True(hasTypeMapAssemblyTargets, "R2R image should contain TypeMapAssemblyTargets section");
+ }
+ }
}
[Fact]