From 406584774a973e347ef307a2b4e2ffacf86c0dd4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Apr 2026 21:52:22 +0000 Subject: [PATCH 01/11] Initial plan From 8378d26d06797986326d40ce7b50598c624f44cb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Apr 2026 22:07:22 +0000 Subject: [PATCH 02/11] Don't preserve forwarder assemblies referenced by link-action root assemblies When an assembly is rooted with `-a` (AllMembers) and has link action, MarkStep.MarkEntireAssembly previously called TypeReferenceMarker.MarkTypeReferences unconditionally. This walked all type references in the assembly and called MarkForwardedScope for each, marking exported type entries in forwarder assemblies and causing those assemblies to be preserved. This is unnecessary for link-action assemblies because SweepStep rewrites type references to point directly to implementation assemblies (via AssemblyReferencesCorrector), making the forwarder assemblies unnecessary. The fix splits MarkEntireAssembly into two concerns: - MarkExportedTypes: always marks the assembly's own ExportedType entries and their forwarded scopes (needed even for link assemblies that are themselves forwarders) - TypeReferenceMarker.MarkTypeReferences: now only called for copy/save assemblies where type references won't be rewritten Also adds a regression test: UsedForwarderIsRemovedWhenReferencedByRootedAssembly. Fixes #126518 Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/620011f6-5d77-4684-9948-36a463ece85e Co-authored-by: sbomer <787361+sbomer@users.noreply.github.com> --- .../src/linker/Linker.Steps/MarkStep.cs | 22 +++++++++++-- ...IsRemovedWhenReferencedByRootedAssembly.cs | 33 +++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderIsRemovedWhenReferencedByRootedAssembly.cs diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index a9a5b3d961bd08..f8669a90382c05 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -1519,8 +1519,26 @@ void MarkEntireAssembly(AssemblyDefinition assembly, MessageOrigin origin) foreach (TypeDefinition type in module.Types) MarkEntireType(type, new DependencyInfo(DependencyKind.TypeInAssembly, assembly), origin); - // Mark scopes of type references and exported types. - TypeReferenceMarker.MarkTypeReferences(assembly, MarkingHelpers); + // Always mark the assembly's own exported types. + MarkExportedTypes(assembly); + + // For copy/save assemblies, also mark scopes of type references, since they won't be rewritten. + // For link assemblies, type references are rewritten by SweepStep to point directly to the + // implementation assembly, so forwarder assemblies referenced only via type references are not needed. + if (ProcessReferencesStep.IsFullyPreservedAction(Annotations.GetAction(assembly))) + TypeReferenceMarker.MarkTypeReferences(assembly, MarkingHelpers); + } + + void MarkExportedTypes(AssemblyDefinition assembly) + { + ModuleDefinition module = assembly.MainModule; + foreach (ExportedType exportedType in module.ExportedTypes) + { + MarkingHelpers.MarkExportedType(exportedType, module, new DependencyInfo(DependencyKind.ExportedType, assembly), new MessageOrigin(assembly)); + // Mark the scope where the forwarded type is defined. Note: nested exported types have a + // DeclaringType scope (not AssemblyNameReference), so MarkForwardedScope will skip them. + MarkingHelpers.MarkForwardedScope(new TypeReference(exportedType.Namespace, exportedType.Name, module, exportedType.Scope), new MessageOrigin(assembly)); + } } sealed class TypeReferenceMarker : TypeReferenceWalker diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderIsRemovedWhenReferencedByRootedAssembly.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderIsRemovedWhenReferencedByRootedAssembly.cs new file mode 100644 index 00000000000000..5a08aade4bc96b --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderIsRemovedWhenReferencedByRootedAssembly.cs @@ -0,0 +1,33 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; +using Mono.Linker.Tests.Cases.TypeForwarding.Dependencies; + +namespace Mono.Linker.Tests.Cases.TypeForwarding +{ + // Actions: + // link - This assembly (default), Forwarder.dll and Implementation.dll + // Rooting the test assembly with -a should not cause Forwarder.dll to be preserved, + // because type references are rewritten to point directly to Implementation.dll. + [SetupLinkerArgument("-a", "test")] + + [SetupCompileBefore("Forwarder.dll", new[] { "Dependencies/ReferenceImplementationLibrary.cs" }, defines: new[] { "INCLUDE_REFERENCE_IMPL" })] + + // After compiling the test case we then replace the reference impl with implementation + type forwarder + [SetupCompileAfter("Implementation.dll", new[] { "Dependencies/ImplementationLibrary.cs" })] + [SetupCompileAfter("Forwarder.dll", new[] { "Dependencies/ForwarderLibrary.cs" }, references: new[] { "Implementation.dll" })] + + [RemovedAssembly("Forwarder.dll")] + [RemovedAssemblyReference("test", "Forwarder")] + [KeptMemberInAssembly("Implementation.dll", typeof(ImplementationLibrary), "GetSomeValue()")] + [KeptMember(".ctor()")] + class UsedForwarderIsRemovedWhenReferencedByRootedAssembly + { + static void Main() + { + new ImplementationLibrary().GetSomeValue(); + } + } +} From 007698d9c045c21942ff3f17267166be387e6da6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Apr 2026 22:50:39 +0000 Subject: [PATCH 03/11] Separate exported type marking from TypeReferenceMarker Move the CreateTypeReferenceForExportedTypeTarget helper out of TypeReferenceMarker and into MarkStep, used by MarkExportedTypes. TypeReferenceMarker.ProcessExportedType is now a no-op since MarkExportedTypes handles all exported type marking (always called before TypeReferenceMarker). This makes TypeReferenceMarker focused solely on marking forwarded scopes of type references. Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/3f923948-9778-4cb0-90d8-1fdfae2875d2 Co-authored-by: sbomer <787361+sbomer@users.noreply.github.com> --- .../src/linker/Linker.Steps/MarkStep.cs | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index f8669a90382c05..bba603b3f3c761 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -1535,12 +1535,22 @@ void MarkExportedTypes(AssemblyDefinition assembly) foreach (ExportedType exportedType in module.ExportedTypes) { MarkingHelpers.MarkExportedType(exportedType, module, new DependencyInfo(DependencyKind.ExportedType, assembly), new MessageOrigin(assembly)); - // Mark the scope where the forwarded type is defined. Note: nested exported types have a - // DeclaringType scope (not AssemblyNameReference), so MarkForwardedScope will skip them. - MarkingHelpers.MarkForwardedScope(new TypeReference(exportedType.Namespace, exportedType.Name, module, exportedType.Scope), new MessageOrigin(assembly)); + MarkingHelpers.MarkForwardedScope(CreateTypeReferenceForExportedTypeTarget(exportedType, module), new MessageOrigin(assembly)); } } + static TypeReference CreateTypeReferenceForExportedTypeTarget(ExportedType exportedType, ModuleDefinition module) + { + TypeReference? declaringTypeReference = null; + if (exportedType.DeclaringType != null) + declaringTypeReference = CreateTypeReferenceForExportedTypeTarget(exportedType.DeclaringType, module); + + return new TypeReference(exportedType.Namespace, exportedType.Name, module, exportedType.Scope) + { + DeclaringType = declaringTypeReference + }; + } + sealed class TypeReferenceMarker : TypeReferenceWalker { @@ -1564,8 +1574,8 @@ protected override void ProcessTypeReference(TypeReference type) protected override void ProcessExportedType(ExportedType exportedType) { - markingHelpers.MarkExportedType(exportedType, assembly.MainModule, new DependencyInfo(DependencyKind.ExportedType, assembly), new MessageOrigin(assembly)); - markingHelpers.MarkForwardedScope(CreateTypeReferenceForExportedTypeTarget(exportedType), new MessageOrigin(assembly)); + // Exported types are handled separately in MarkExportedTypes, which is always called + // before TypeReferenceMarker. Nothing to do here. } protected override void ProcessExtra() @@ -1579,20 +1589,6 @@ protected override void ProcessExtra() markingHelpers.MarkForwardedScope(typeReference, new MessageOrigin(assembly)); } } - - TypeReference CreateTypeReferenceForExportedTypeTarget(ExportedType exportedType) - { - TypeReference? declaringTypeReference = null; - if (exportedType.DeclaringType != null) - { - declaringTypeReference = CreateTypeReferenceForExportedTypeTarget(exportedType.DeclaringType); - } - - return new TypeReference(exportedType.Namespace, exportedType.Name, assembly.MainModule, exportedType.Scope) - { - DeclaringType = declaringTypeReference - }; - } } void ProcessModuleType(AssemblyDefinition assembly, MessageOrigin origin) From 320ffb8aa1573c4e64bd2da583712d6726aafc36 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Apr 2026 23:50:21 +0000 Subject: [PATCH 04/11] Move TypeReferenceMarker call to copy/save branch in MarkAssembly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MarkEntireAssembly now only marks all types, custom attributes, and exported types — no action check needed. TypeReferenceMarker.MarkTypeReferences is called directly in the copy/save branch of MarkAssembly, where it conceptually belongs. The link-action root path calls MarkEntireAssembly without the type reference scope walk, since SweepStep rewrites those references anyway. Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/d97f6dea-e5d1-499b-b24e-384fc89ddca7 Co-authored-by: sbomer <787361+sbomer@users.noreply.github.com> --- src/tools/illink/src/linker/Linker.Steps/MarkStep.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index bba603b3f3c761..61aca9c0afa681 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -1489,6 +1489,8 @@ public virtual void MarkAssembly(AssemblyDefinition assembly, DependencyInfo rea disableMarkingOfCopyAssembliesValue != "true") { MarkEntireAssembly(assembly, assemblyOrigin); + // For copy/save assemblies, also mark scopes of type references, since they won't be rewritten. + TypeReferenceMarker.MarkTypeReferences(assembly, MarkingHelpers); } return; } @@ -1519,14 +1521,7 @@ void MarkEntireAssembly(AssemblyDefinition assembly, MessageOrigin origin) foreach (TypeDefinition type in module.Types) MarkEntireType(type, new DependencyInfo(DependencyKind.TypeInAssembly, assembly), origin); - // Always mark the assembly's own exported types. MarkExportedTypes(assembly); - - // For copy/save assemblies, also mark scopes of type references, since they won't be rewritten. - // For link assemblies, type references are rewritten by SweepStep to point directly to the - // implementation assembly, so forwarder assemblies referenced only via type references are not needed. - if (ProcessReferencesStep.IsFullyPreservedAction(Annotations.GetAction(assembly))) - TypeReferenceMarker.MarkTypeReferences(assembly, MarkingHelpers); } void MarkExportedTypes(AssemblyDefinition assembly) From 878c7e62f963dd4f8438e405aa5452bbc3a2670b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 18:49:06 +0000 Subject: [PATCH 05/11] Separate MarkExportedType from MarkForwardedScope in exported type handling MarkExportedTypes now only marks the export entry itself (MarkExportedType) for all root assemblies. MarkForwardedScope for exported types is moved back into TypeReferenceMarker.ProcessExportedType, which already only runs for copy/save assemblies. This keeps the two concerns separate: marking the forwarder entry vs. preserving the assembly it points to. Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/a0ee3bc2-d2ba-46c6-9177-76b764d96d85 Co-authored-by: sbomer <787361+sbomer@users.noreply.github.com> --- src/tools/illink/src/linker/Linker.Steps/MarkStep.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index 61aca9c0afa681..15b3427fe4ecce 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -1528,10 +1528,7 @@ void MarkExportedTypes(AssemblyDefinition assembly) { ModuleDefinition module = assembly.MainModule; foreach (ExportedType exportedType in module.ExportedTypes) - { MarkingHelpers.MarkExportedType(exportedType, module, new DependencyInfo(DependencyKind.ExportedType, assembly), new MessageOrigin(assembly)); - MarkingHelpers.MarkForwardedScope(CreateTypeReferenceForExportedTypeTarget(exportedType, module), new MessageOrigin(assembly)); - } } static TypeReference CreateTypeReferenceForExportedTypeTarget(ExportedType exportedType, ModuleDefinition module) @@ -1569,8 +1566,7 @@ protected override void ProcessTypeReference(TypeReference type) protected override void ProcessExportedType(ExportedType exportedType) { - // Exported types are handled separately in MarkExportedTypes, which is always called - // before TypeReferenceMarker. Nothing to do here. + markingHelpers.MarkForwardedScope(MarkStep.CreateTypeReferenceForExportedTypeTarget(exportedType, assembly.MainModule), new MessageOrigin(assembly)); } protected override void ProcessExtra() From f92e23ad07323d9baf3b7e3e05f37662422eccb3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 19:07:05 +0000 Subject: [PATCH 06/11] Move CreateTypeReferenceForExportedTypeTarget into TypeReferenceMarker The helper is only used by TypeReferenceMarker.ProcessExportedType, so it belongs as a private static method inside the TypeReferenceMarker nested class. Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/d9d13df1-5cf7-4cf6-a3f8-25a9c90b4192 Co-authored-by: sbomer <787361+sbomer@users.noreply.github.com> --- .../src/linker/Linker.Steps/MarkStep.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index 15b3427fe4ecce..587a91e558a463 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -1531,18 +1531,6 @@ void MarkExportedTypes(AssemblyDefinition assembly) MarkingHelpers.MarkExportedType(exportedType, module, new DependencyInfo(DependencyKind.ExportedType, assembly), new MessageOrigin(assembly)); } - static TypeReference CreateTypeReferenceForExportedTypeTarget(ExportedType exportedType, ModuleDefinition module) - { - TypeReference? declaringTypeReference = null; - if (exportedType.DeclaringType != null) - declaringTypeReference = CreateTypeReferenceForExportedTypeTarget(exportedType.DeclaringType, module); - - return new TypeReference(exportedType.Namespace, exportedType.Name, module, exportedType.Scope) - { - DeclaringType = declaringTypeReference - }; - } - sealed class TypeReferenceMarker : TypeReferenceWalker { @@ -1566,7 +1554,19 @@ protected override void ProcessTypeReference(TypeReference type) protected override void ProcessExportedType(ExportedType exportedType) { - markingHelpers.MarkForwardedScope(MarkStep.CreateTypeReferenceForExportedTypeTarget(exportedType, assembly.MainModule), new MessageOrigin(assembly)); + markingHelpers.MarkForwardedScope(CreateTypeReferenceForExportedTypeTarget(exportedType, assembly.MainModule), new MessageOrigin(assembly)); + } + + static TypeReference CreateTypeReferenceForExportedTypeTarget(ExportedType exportedType, ModuleDefinition module) + { + TypeReference? declaringTypeReference = null; + if (exportedType.DeclaringType != null) + declaringTypeReference = CreateTypeReferenceForExportedTypeTarget(exportedType.DeclaringType, module); + + return new TypeReference(exportedType.Namespace, exportedType.Name, module, exportedType.Scope) + { + DeclaringType = declaringTypeReference + }; } protected override void ProcessExtra() From 7a0b815ee11b6caceb41f439846ad608c85b76e1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 19:15:56 +0000 Subject: [PATCH 07/11] Move CreateTypeReferenceForExportedTypeTarget after ProcessExtra in TypeReferenceMarker Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/3f440042-19b5-4d13-8a39-ad4bb84e9161 Co-authored-by: sbomer <787361+sbomer@users.noreply.github.com> --- .../src/linker/Linker.Steps/MarkStep.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index 587a91e558a463..a3b4a63e6ec450 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -1557,18 +1557,6 @@ protected override void ProcessExportedType(ExportedType exportedType) markingHelpers.MarkForwardedScope(CreateTypeReferenceForExportedTypeTarget(exportedType, assembly.MainModule), new MessageOrigin(assembly)); } - static TypeReference CreateTypeReferenceForExportedTypeTarget(ExportedType exportedType, ModuleDefinition module) - { - TypeReference? declaringTypeReference = null; - if (exportedType.DeclaringType != null) - declaringTypeReference = CreateTypeReferenceForExportedTypeTarget(exportedType.DeclaringType, module); - - return new TypeReference(exportedType.Namespace, exportedType.Name, module, exportedType.Scope) - { - DeclaringType = declaringTypeReference - }; - } - protected override void ProcessExtra() { // Also mark the scopes of metadata typeref rows to cover any not discovered by the traversal. @@ -1580,6 +1568,18 @@ protected override void ProcessExtra() markingHelpers.MarkForwardedScope(typeReference, new MessageOrigin(assembly)); } } + + static TypeReference CreateTypeReferenceForExportedTypeTarget(ExportedType exportedType, ModuleDefinition module) + { + TypeReference? declaringTypeReference = null; + if (exportedType.DeclaringType != null) + declaringTypeReference = CreateTypeReferenceForExportedTypeTarget(exportedType.DeclaringType, module); + + return new TypeReference(exportedType.Namespace, exportedType.Name, module, exportedType.Scope) + { + DeclaringType = declaringTypeReference + }; + } } void ProcessModuleType(AssemblyDefinition assembly, MessageOrigin origin) From 830933a0d456cbba90724d6b0fa96bcce7fcbbd0 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Mon, 6 Apr 2026 12:19:34 -0700 Subject: [PATCH 08/11] Revert CreateTypeReferenceForExportedTypeTarget to instance method Undo the static refactoring of CreateTypeReferenceForExportedTypeTarget, keeping it as an instance method on TypeReferenceMarker that captures assembly.MainModule from the enclosing class. --- src/tools/illink/src/linker/Linker.Steps/MarkStep.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index a3b4a63e6ec450..f5c14ff78315aa 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -1554,7 +1554,7 @@ protected override void ProcessTypeReference(TypeReference type) protected override void ProcessExportedType(ExportedType exportedType) { - markingHelpers.MarkForwardedScope(CreateTypeReferenceForExportedTypeTarget(exportedType, assembly.MainModule), new MessageOrigin(assembly)); + markingHelpers.MarkForwardedScope(CreateTypeReferenceForExportedTypeTarget(exportedType), new MessageOrigin(assembly)); } protected override void ProcessExtra() @@ -1569,13 +1569,15 @@ protected override void ProcessExtra() } } - static TypeReference CreateTypeReferenceForExportedTypeTarget(ExportedType exportedType, ModuleDefinition module) + TypeReference CreateTypeReferenceForExportedTypeTarget(ExportedType exportedType) { TypeReference? declaringTypeReference = null; if (exportedType.DeclaringType != null) - declaringTypeReference = CreateTypeReferenceForExportedTypeTarget(exportedType.DeclaringType, module); + { + declaringTypeReference = CreateTypeReferenceForExportedTypeTarget(exportedType.DeclaringType); + } - return new TypeReference(exportedType.Namespace, exportedType.Name, module, exportedType.Scope) + return new TypeReference(exportedType.Namespace, exportedType.Name, assembly.MainModule, exportedType.Scope) { DeclaringType = declaringTypeReference }; From cbbf72e3b501017dacddb3438c6d4b9668ac7f07 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 20:38:07 +0000 Subject: [PATCH 09/11] Add test for Finding 2: rooted forwarder assembly with exported types is handled Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/6d212a0c-aaf9-43f6-9464-dc0e37b7b97f Co-authored-by: sbomer <787361+sbomer@users.noreply.github.com> --- ...otedForwarderWithExportedTypesIsHandled.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/TypeForwarding/RootedForwarderWithExportedTypesIsHandled.cs diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/TypeForwarding/RootedForwarderWithExportedTypesIsHandled.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/TypeForwarding/RootedForwarderWithExportedTypesIsHandled.cs new file mode 100644 index 00000000000000..c5ba51360837ed --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/TypeForwarding/RootedForwarderWithExportedTypesIsHandled.cs @@ -0,0 +1,34 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; +using Mono.Linker.Tests.Cases.TypeForwarding.Dependencies; + +namespace Mono.Linker.Tests.Cases.TypeForwarding +{ + // Actions: + // link - This assembly (default), Forwarder.dll and Implementation.dll + // Rooting Forwarder.dll with -a (link action) while it is itself a type forwarder: + // MarkExportedTypes marks the exported type entries without TypeReferenceMarker running, + // and the implementation assembly is still preserved because the forwarded type is used. + [SetupLinkerArgument ("-a", "Forwarder")] + + [SetupCompileBefore ("Forwarder.dll", new[] { "Dependencies/ReferenceImplementationLibrary.cs" }, defines: new[] { "INCLUDE_REFERENCE_IMPL" })] + + // After compiling the test case we then replace the reference impl with implementation + type forwarder + [SetupCompileAfter ("Implementation.dll", new[] { "Dependencies/ImplementationLibrary.cs" })] + [SetupCompileAfter ("Forwarder.dll", new[] { "Dependencies/ForwarderLibrary.cs" }, references: new[] { "Implementation.dll" })] + + [KeptAssembly ("Forwarder.dll")] + [KeptTypeInAssembly ("Forwarder.dll", typeof (ImplementationLibrary))] + [RemovedAssemblyReference ("test", "Forwarder")] + [KeptMemberInAssembly ("Implementation.dll", typeof (ImplementationLibrary), "GetSomeValue()")] + class RootedForwarderWithExportedTypesIsHandled + { + static void Main () + { + new ImplementationLibrary ().GetSomeValue (); + } + } +} From c53905a05abf80f1639e6dfee2db544b237ddd40 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Apr 2026 18:40:36 +0000 Subject: [PATCH 10/11] Apply review feedback: fix indentation in test file and pass origin to MarkExportedTypes Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/0b1ef5fb-462b-4dc2-b733-efa071753b64 Co-authored-by: sbomer <787361+sbomer@users.noreply.github.com> --- .../src/linker/Linker.Steps/MarkStep.cs | 6 +-- ...otedForwarderWithExportedTypesIsHandled.cs | 42 +++++++++---------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index f5c14ff78315aa..369e23aebdf3a4 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -1521,14 +1521,14 @@ void MarkEntireAssembly(AssemblyDefinition assembly, MessageOrigin origin) foreach (TypeDefinition type in module.Types) MarkEntireType(type, new DependencyInfo(DependencyKind.TypeInAssembly, assembly), origin); - MarkExportedTypes(assembly); + MarkExportedTypes(assembly, origin); } - void MarkExportedTypes(AssemblyDefinition assembly) + void MarkExportedTypes(AssemblyDefinition assembly, MessageOrigin origin) { ModuleDefinition module = assembly.MainModule; foreach (ExportedType exportedType in module.ExportedTypes) - MarkingHelpers.MarkExportedType(exportedType, module, new DependencyInfo(DependencyKind.ExportedType, assembly), new MessageOrigin(assembly)); + MarkingHelpers.MarkExportedType(exportedType, module, new DependencyInfo(DependencyKind.ExportedType, assembly), origin); } sealed class TypeReferenceMarker : TypeReferenceWalker diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/TypeForwarding/RootedForwarderWithExportedTypesIsHandled.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/TypeForwarding/RootedForwarderWithExportedTypesIsHandled.cs index c5ba51360837ed..40dec0e75f74bc 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/TypeForwarding/RootedForwarderWithExportedTypesIsHandled.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/TypeForwarding/RootedForwarderWithExportedTypesIsHandled.cs @@ -7,28 +7,28 @@ namespace Mono.Linker.Tests.Cases.TypeForwarding { - // Actions: - // link - This assembly (default), Forwarder.dll and Implementation.dll - // Rooting Forwarder.dll with -a (link action) while it is itself a type forwarder: - // MarkExportedTypes marks the exported type entries without TypeReferenceMarker running, - // and the implementation assembly is still preserved because the forwarded type is used. - [SetupLinkerArgument ("-a", "Forwarder")] + // Actions: + // link - This assembly (default), Forwarder.dll and Implementation.dll + // Rooting Forwarder.dll with -a (link action) while it is itself a type forwarder: + // MarkExportedTypes marks the exported type entries without TypeReferenceMarker running, + // and the implementation assembly is still preserved because the forwarded type is used. + [SetupLinkerArgument("-a", "Forwarder")] - [SetupCompileBefore ("Forwarder.dll", new[] { "Dependencies/ReferenceImplementationLibrary.cs" }, defines: new[] { "INCLUDE_REFERENCE_IMPL" })] + [SetupCompileBefore("Forwarder.dll", new[] { "Dependencies/ReferenceImplementationLibrary.cs" }, defines: new[] { "INCLUDE_REFERENCE_IMPL" })] - // After compiling the test case we then replace the reference impl with implementation + type forwarder - [SetupCompileAfter ("Implementation.dll", new[] { "Dependencies/ImplementationLibrary.cs" })] - [SetupCompileAfter ("Forwarder.dll", new[] { "Dependencies/ForwarderLibrary.cs" }, references: new[] { "Implementation.dll" })] + // After compiling the test case we then replace the reference impl with implementation + type forwarder + [SetupCompileAfter("Implementation.dll", new[] { "Dependencies/ImplementationLibrary.cs" })] + [SetupCompileAfter("Forwarder.dll", new[] { "Dependencies/ForwarderLibrary.cs" }, references: new[] { "Implementation.dll" })] - [KeptAssembly ("Forwarder.dll")] - [KeptTypeInAssembly ("Forwarder.dll", typeof (ImplementationLibrary))] - [RemovedAssemblyReference ("test", "Forwarder")] - [KeptMemberInAssembly ("Implementation.dll", typeof (ImplementationLibrary), "GetSomeValue()")] - class RootedForwarderWithExportedTypesIsHandled - { - static void Main () - { - new ImplementationLibrary ().GetSomeValue (); - } - } + [KeptAssembly("Forwarder.dll")] + [KeptTypeInAssembly("Forwarder.dll", typeof(ImplementationLibrary))] + [RemovedAssemblyReference("test", "Forwarder")] + [KeptMemberInAssembly("Implementation.dll", typeof(ImplementationLibrary), "GetSomeValue()")] + class RootedForwarderWithExportedTypesIsHandled + { + static void Main() + { + new ImplementationLibrary().GetSomeValue(); + } + } } From cde38d0aec4695c2f8ac3a6febf7a15f7c4b2a30 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Wed, 15 Apr 2026 11:21:42 -0700 Subject: [PATCH 11/11] Add new TypeForwarding tests to ILTrim expected failures ILTrim does not support ExportedType/TypeForwarder metadata, so these new test cases cannot pass. Add them to the expected failures list: - TypeForwarding.RootedForwarderWithExportedTypesIsHandled - TypeForwarding.UsedForwarderIsRemovedWhenReferencedByRootedAssembly Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/tools/ILTrim.Tests/ILTrimExpectedFailures.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/coreclr/tools/ILTrim.Tests/ILTrimExpectedFailures.txt b/src/coreclr/tools/ILTrim.Tests/ILTrimExpectedFailures.txt index 90c596ee59fa81..88163a1f32ffef 100644 --- a/src/coreclr/tools/ILTrim.Tests/ILTrimExpectedFailures.txt +++ b/src/coreclr/tools/ILTrim.Tests/ILTrimExpectedFailures.txt @@ -519,6 +519,7 @@ TypeForwarding.AttributesScopeUpdated TypeForwarding.MultiForwardedTypesWithCopyUsed TypeForwarding.MultiForwardedTypesWithLink TypeForwarding.NestedTypeForwarder +TypeForwarding.RootedForwarderWithExportedTypesIsHandled TypeForwarding.SecurityAttributeScope TypeForwarding.TypeForwardedIsUpdatedForMissingType TypeForwarding.TypeForwarderOnlyAssemblyCanBePreservedViaLinkXml @@ -537,6 +538,7 @@ TypeForwarding.UsedForwarderInCopyAssemblyKeptByUsedProperty TypeForwarding.UsedForwarderInCopyAssemblyKeptByUsedTypeAsGenericArg TypeForwarding.UsedForwarderInGenericIsDynamicallyAccessedWithAssemblyCopyUsed TypeForwarding.UsedForwarderIsDynamicallyAccessedWithAssemblyCopyUsed +TypeForwarding.UsedForwarderIsRemovedWhenReferencedByRootedAssembly TypeForwarding.UsedForwarderWithAssemblyCopy TypeForwarding.UsedForwarderWithAssemblyCopyUsed TypeForwarding.UsedForwarderWithAssemblyCopyUsedAndForwarderLibraryKept