From 31261d236f490816832a0a24deca95feec10239f Mon Sep 17 00:00:00 2001 From: Mateo Torres Ruiz Date: Fri, 26 Feb 2021 08:28:31 -0800 Subject: [PATCH 1/6] Keep facades of dynamically accessed exported types --- src/linker/Linker.Steps/MarkStep.cs | 5 +++ src/linker/Linker/MarkingHelpers.cs | 25 +++++++++-- .../Linker/ModuleDefinitionExtensions.cs | 15 +++++++ src/linker/Linker/TypeNameResolver.cs | 8 +++- ...DynamicallyAccessedWithAssemblyCopyUsed.cs | 40 +++++++++++++++++ ...ransitiveForwarderIsDynamicallyAccessed.cs | 43 +++++++++++++++++++ 6 files changed, 131 insertions(+), 5 deletions(-) create mode 100644 test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderIsDynamicallyAccessedWithAssemblyCopyUsed.cs create mode 100644 test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderIsDynamicallyAccessed.cs diff --git a/src/linker/Linker.Steps/MarkStep.cs b/src/linker/Linker.Steps/MarkStep.cs index 1117b33f3939..1642fc0be522 100644 --- a/src/linker/Linker.Steps/MarkStep.cs +++ b/src/linker/Linker.Steps/MarkStep.cs @@ -824,6 +824,11 @@ void MarkDynamicDependency (DynamicDependency dynamicDependency, IMemberDefiniti _context.LogWarning ($"Unresolved type '{typeName}' in DynamicDependencyAttribute", 2036, context); return; } + + var module = assembly.MainModule; + if (module.GetMatchingExportedType (type, out var exportedType)) { + _context.MarkingHelpers.MarkExportedType (exportedType, module, new DependencyInfo (DependencyKind.ModuleOfExportedType, module)); + } } else if (dynamicDependency.Type is TypeReference typeReference) { type = typeReference.Resolve (); if (type == null) { diff --git a/src/linker/Linker/MarkingHelpers.cs b/src/linker/Linker/MarkingHelpers.cs index aa5d95b258e8..b9390247e1a2 100644 --- a/src/linker/Linker/MarkingHelpers.cs +++ b/src/linker/Linker/MarkingHelpers.cs @@ -1,4 +1,3 @@ -using System; using Mono.Cecil; namespace Mono.Linker @@ -12,12 +11,30 @@ public MarkingHelpers (LinkContext context) _context = context; } - public void MarkExportedType (ExportedType type, ModuleDefinition module, in DependencyInfo reason) + public void MarkExportedType (ExportedType exportedType, ModuleDefinition module, in DependencyInfo reason) { - if (!_context.Annotations.MarkProcessed (type, reason)) + if (!_context.Annotations.MarkProcessed (exportedType, reason)) return; + + if (reason.Kind == DependencyKind.ModuleOfExportedType) { + var facadeAssembly = (reason.Source as ModuleDefinition).Assembly; + TypeDefinition resolvedType = exportedType.Resolve (); + while (facadeAssembly.FullName != resolvedType.Module.Assembly.FullName) { + _context.Annotations.Mark (module, new DependencyInfo (DependencyKind.ModuleOfExportedType, exportedType)); + foreach (var assemblyReference in module.AssemblyReferences) { + if (assemblyReference.MetadataToken == exportedType.Scope.MetadataToken) { + facadeAssembly = module.AssemblyResolver.Resolve (assemblyReference); + module = facadeAssembly.MainModule; + break; + } + } + } + + return; + } + if (_context.KeepTypeForwarderOnlyAssemblies) - _context.Annotations.Mark (module, new DependencyInfo (DependencyKind.ModuleOfExportedType, type)); + _context.Annotations.Mark (module, new DependencyInfo (DependencyKind.ModuleOfExportedType, exportedType)); } } } diff --git a/src/linker/Linker/ModuleDefinitionExtensions.cs b/src/linker/Linker/ModuleDefinitionExtensions.cs index f846c96f40e3..802207601e7c 100644 --- a/src/linker/Linker/ModuleDefinitionExtensions.cs +++ b/src/linker/Linker/ModuleDefinitionExtensions.cs @@ -11,6 +11,21 @@ public static bool IsCrossgened (this ModuleDefinition module) (module.Attributes & ModuleAttributes.ILLibrary) != 0; } + public static bool GetMatchingExportedType (this ModuleDefinition module, TypeDefinition typeDefinition, out ExportedType exportedType) + { + exportedType = null; + if (!module.HasExportedTypes || typeDefinition == null) + return false; + + foreach (var et in module.ExportedTypes) + if (et.FullName == typeDefinition.FullName) { + exportedType = et; + return true; + } + + return false; + } + public static TypeDefinition ResolveType (this ModuleDefinition module, string typeFullName) { if (typeFullName == null) diff --git a/src/linker/Linker/TypeNameResolver.cs b/src/linker/Linker/TypeNameResolver.cs index 3eaa5556bb02..664599e16d9a 100644 --- a/src/linker/Linker/TypeNameResolver.cs +++ b/src/linker/Linker/TypeNameResolver.cs @@ -86,7 +86,13 @@ TypeReference ResolveTypeName (AssemblyDefinition assembly, TypeName typeName) }; } - return assembly.MainModule.ResolveType (typeName.ToString ()); + ModuleDefinition module = assembly.MainModule; + TypeDefinition typeDefinition = module.ResolveType (typeName.ToString ()); + if (module.GetMatchingExportedType (typeDefinition, out var exportedType)) { + _context.MarkingHelpers.MarkExportedType (exportedType, module, new DependencyInfo (DependencyKind.ModuleOfExportedType, module)); + } + + return typeDefinition; } } } \ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderIsDynamicallyAccessedWithAssemblyCopyUsed.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderIsDynamicallyAccessedWithAssemblyCopyUsed.cs new file mode 100644 index 000000000000..d897e8247e81 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderIsDynamicallyAccessedWithAssemblyCopyUsed.cs @@ -0,0 +1,40 @@ +// 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. + +using System.Diagnostics.CodeAnalysis; +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 +{ + + [SetupLinkerAction ("link", "test")] + [SetupLinkerUserAction ("copyused")] + [KeepTypeForwarderOnlyAssemblies ("false")] + + [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")] + [KeptMemberInAssembly ("Implementation.dll", typeof (ImplementationLibrary), "GetSomeValue()")] + [RemovedForwarder ("Forwarder.dll", nameof (ImplementationStruct))] + class UsedForwarderIsDynamicallyAccessedWithAssemblyCopyUsed + { + static void Main () + { + PointToTypeInFacade ("Mono.Linker.Tests.Cases.TypeForwarding.Dependencies.ImplementationLibrary, Forwarder"); + } + + [Kept] + static void PointToTypeInFacade ( + [KeptAttributeAttribute (typeof(DynamicallyAccessedMembersAttribute))] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] string typeName) + { + } + } +} diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderIsDynamicallyAccessed.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderIsDynamicallyAccessed.cs new file mode 100644 index 000000000000..990e5fbc8672 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderIsDynamicallyAccessed.cs @@ -0,0 +1,43 @@ +// 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. + +using System.Diagnostics.CodeAnalysis; +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 +{ + [SetupLinkerAction ("link", "test")] + [SetupLinkerUserAction ("copyused")] + [KeepTypeForwarderOnlyAssemblies ("false")] + + [SetupCompileBefore ("SecondForwarder.dll", new[] { "Dependencies/ReferenceImplementationLibrary.cs" }, defines: new[] { "INCLUDE_REFERENCE_IMPL" })] + [SetupCompileBefore ("FirstForwarder.dll", new[] { "Dependencies/ForwarderLibrary.cs" }, references: new[] { "SecondForwarder.dll" })] + + // After compiling the test case we then replace the reference impl with implementation + type forwarder + [SetupCompileAfter ("Implementation.dll", new[] { "Dependencies/ImplementationLibrary.cs" })] + [SetupCompileAfter ("SecondForwarder.dll", new[] { "Dependencies/ForwarderLibrary.cs" }, references: new[] { "Implementation.dll" })] + + [KeptAssembly ("FirstForwarder.dll")] + [KeptAssembly ("SecondForwarder.dll")] + [KeptMemberInAssembly ("Implementation.dll", typeof (ImplementationLibrary), "GetSomeValue()")] + [RemovedForwarder ("FirstForwarder.dll", nameof (ImplementationStruct))] + [RemovedForwarder ("SecondForwarder.dll", nameof (ImplementationStruct))] + class UsedTransitiveForwarderIsDynamicallyAccessed + { + static void Main () + { + // FirstForwarder -> SecondForwarder -> Implementation + PointToTypeInFacade ("Mono.Linker.Tests.Cases.TypeForwarding.Dependencies.ImplementationLibrary, FirstForwarder"); + } + + [Kept] + static void PointToTypeInFacade ( + [KeptAttributeAttribute (typeof(DynamicallyAccessedMembersAttribute))] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] string typeName) + { + } + } +} From 724fee5332de77cf321acbfee4a6436258dc2c4b Mon Sep 17 00:00:00 2001 From: Mateo Torres Ruiz Date: Fri, 26 Feb 2021 09:45:23 -0800 Subject: [PATCH 2/6] Fix tests --- .../UsedForwarderIsDynamicallyAccessedWithAssemblyCopyUsed.cs | 2 +- .../UsedTransitiveForwarderIsDynamicallyAccessed.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderIsDynamicallyAccessedWithAssemblyCopyUsed.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderIsDynamicallyAccessedWithAssemblyCopyUsed.cs index d897e8247e81..6a4cf93871aa 100644 --- a/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderIsDynamicallyAccessedWithAssemblyCopyUsed.cs +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderIsDynamicallyAccessedWithAssemblyCopyUsed.cs @@ -11,7 +11,7 @@ namespace Mono.Linker.Tests.Cases.TypeForwarding { [SetupLinkerAction ("link", "test")] - [SetupLinkerUserAction ("copyused")] + [SetupLinkerDefaultAction ("copyused")] [KeepTypeForwarderOnlyAssemblies ("false")] [SetupCompileBefore ("Forwarder.dll", new[] { "Dependencies/ReferenceImplementationLibrary.cs" }, defines: new[] { "INCLUDE_REFERENCE_IMPL" })] diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderIsDynamicallyAccessed.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderIsDynamicallyAccessed.cs index 990e5fbc8672..20aee4f3c971 100644 --- a/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderIsDynamicallyAccessed.cs +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderIsDynamicallyAccessed.cs @@ -10,7 +10,7 @@ namespace Mono.Linker.Tests.Cases.TypeForwarding { [SetupLinkerAction ("link", "test")] - [SetupLinkerUserAction ("copyused")] + [SetupLinkerDefaultAction ("copyused")] [KeepTypeForwarderOnlyAssemblies ("false")] [SetupCompileBefore ("SecondForwarder.dll", new[] { "Dependencies/ReferenceImplementationLibrary.cs" }, defines: new[] { "INCLUDE_REFERENCE_IMPL" })] From 1099f10a42efdd8e137a9a5743c88ad4ca6fa0d3 Mon Sep 17 00:00:00 2001 From: Mateo Torres Ruiz Date: Mon, 1 Mar 2021 20:28:46 -0800 Subject: [PATCH 3/6] PR Feedback --- .../ReflectionMethodBodyScanner.cs | 6 ++- src/linker/Linker.Steps/DescriptorMarker.cs | 3 ++ src/linker/Linker.Steps/MarkStep.cs | 53 ++++--------------- src/linker/Linker.Steps/SweepStep.cs | 8 +++ src/linker/Linker/MarkingHelpers.cs | 20 +------ src/linker/Linker/TypeNameResolver.cs | 18 ++++--- .../LinkXml/CanPreserveAnExportedType.cs | 1 - .../CanPreserveExportedTypesUsingRegex.cs | 1 - .../UsedNonRequiredExportedTypeIsKept.cs | 1 - ...ransitiveForwarderIsDynamicallyAccessed.cs | 3 +- 10 files changed, 39 insertions(+), 75 deletions(-) diff --git a/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs b/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs index c4718503b959..ad800c295016 100644 --- a/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs +++ b/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs @@ -897,7 +897,7 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c } foreach (var typeNameValue in methodParams[0].UniqueValues ()) { if (typeNameValue is KnownStringValue knownStringValue) { - TypeReference foundTypeRef = _context.TypeNameResolver.ResolveTypeName (knownStringValue.Contents); + TypeReference foundTypeRef = _context.TypeNameResolver.ResolveTypeName (knownStringValue.Contents).Type; TypeDefinition foundType = foundTypeRef?.ResolveToMainTypeDefinition (); if (foundType == null) { // Intentionally ignore - it's not wrong for code to call Type.GetType on non-existing name, the code might expect null/exception back. @@ -1804,7 +1804,7 @@ void RequireDynamicallyAccessedMembers (ref ReflectionPatternContext reflectionC } else if (uniqueValue is SystemTypeValue systemTypeValue) { MarkTypeForDynamicallyAccessedMembers (ref reflectionContext, systemTypeValue.TypeRepresented, requiredMemberTypes); } else if (uniqueValue is KnownStringValue knownStringValue) { - TypeReference typeRef = _context.TypeNameResolver.ResolveTypeName (knownStringValue.Contents); + (AssemblyDefinition assembly, TypeReference typeRef) = _context.TypeNameResolver.ResolveTypeName (knownStringValue.Contents); TypeDefinition foundType = typeRef?.ResolveToMainTypeDefinition (); if (foundType == null) { // Intentionally ignore - it's not wrong for code to call Type.GetType on non-existing name, the code might expect null/exception back. @@ -1812,6 +1812,8 @@ void RequireDynamicallyAccessedMembers (ref ReflectionPatternContext reflectionC } else { MarkType (ref reflectionContext, typeRef); MarkTypeForDynamicallyAccessedMembers (ref reflectionContext, foundType, requiredMemberTypes); + if (assembly.MainModule.GetMatchingExportedType (foundType, out var exportedType)) + _context.MarkingHelpers.MarkExportedType (exportedType, typeRef.Module, new DependencyInfo (DependencyKind.DynamicallyAccessedMember, foundType)); } } else if (uniqueValue == NullValue.Instance) { // Ignore - probably unreachable path as it would fail at runtime anyway. diff --git a/src/linker/Linker.Steps/DescriptorMarker.cs b/src/linker/Linker.Steps/DescriptorMarker.cs index a8b347cca993..d46c1896a1d1 100644 --- a/src/linker/Linker.Steps/DescriptorMarker.cs +++ b/src/linker/Linker.Steps/DescriptorMarker.cs @@ -45,6 +45,9 @@ protected override void ProcessAssembly (AssemblyDefinition assembly, XPathNavig if (GetTypePreserve (nav) == TypePreserve.All) { foreach (var type in assembly.MainModule.Types) MarkAndPreserveAll (type); + + foreach (var exportedType in assembly.MainModule.ExportedTypes) + _context.MarkingHelpers.MarkExportedType (exportedType, assembly.MainModule, new DependencyInfo (DependencyKind.ModuleOfExportedType, assembly.MainModule)); } else { ProcessTypes (assembly, nav, warnOnUnresolvedTypes); ProcessNamespaces (assembly, nav); diff --git a/src/linker/Linker.Steps/MarkStep.cs b/src/linker/Linker.Steps/MarkStep.cs index 1642fc0be522..2780c1547042 100644 --- a/src/linker/Linker.Steps/MarkStep.cs +++ b/src/linker/Linker.Steps/MarkStep.cs @@ -331,41 +331,7 @@ private void MarkEntireTypeInternal (TypeDefinition type, bool includeBaseTypes, void Process () { - while (ProcessPrimaryQueue () || ProcessMarkedPending () || ProcessLazyAttributes () || ProcessLateMarkedAttributes () || MarkFullyPreservedAssemblies ()) { - - // deal with [TypeForwardedTo] pseudo-attributes - - // This marks all exported types that resolve to a marked type. Note that it - // may mark unused exported types that happen to resolve to a type which was - // marked from a different reference. This seems incorrect. - // Note also that we may still remove type forwarder assemblies with marked exports in SweepStep, - // if they don't contain other used code. - // https://github.com/mono/linker/issues/1740 - foreach (AssemblyDefinition assembly in _context.GetAssemblies ()) { - if (!assembly.MainModule.HasExportedTypes) - continue; - - foreach (var exported in assembly.MainModule.ExportedTypes) { - bool isForwarder = exported.IsForwarder; - var declaringType = exported.DeclaringType; - while (!isForwarder && (declaringType != null)) { - isForwarder = declaringType.IsForwarder; - declaringType = declaringType.DeclaringType; - } - - if (!isForwarder) - continue; - TypeDefinition type = exported.Resolve (); - if (type == null) - continue; - if (!Annotations.IsMarked (type)) - continue; - var di = new DependencyInfo (DependencyKind.ExportedType, type); - _context.MarkingHelpers.MarkExportedType (exported, assembly.MainModule, di); - } - } - } - + while (ProcessPrimaryQueue () || ProcessMarkedPending () || ProcessLazyAttributes () || ProcessLateMarkedAttributes () || MarkFullyPreservedAssemblies ()) ; ProcessPendingTypeChecks (); } @@ -824,11 +790,6 @@ void MarkDynamicDependency (DynamicDependency dynamicDependency, IMemberDefiniti _context.LogWarning ($"Unresolved type '{typeName}' in DynamicDependencyAttribute", 2036, context); return; } - - var module = assembly.MainModule; - if (module.GetMatchingExportedType (type, out var exportedType)) { - _context.MarkingHelpers.MarkExportedType (exportedType, module, new DependencyInfo (DependencyKind.ModuleOfExportedType, module)); - } } else if (dynamicDependency.Type is TypeReference typeReference) { type = typeReference.Resolve (); if (type == null) { @@ -843,6 +804,13 @@ void MarkDynamicDependency (DynamicDependency dynamicDependency, IMemberDefiniti } } + ModuleDefinition module = assembly.MainModule; + if (module.HasExportedTypes) { + foreach (var exportedType in module.ExportedTypes) + if (exportedType.FullName == type.FullName) + _context.MarkingHelpers.MarkExportedType (exportedType, module, new DependencyInfo (DependencyKind.DynamicDependency, type)); + } + IEnumerable members; if (dynamicDependency.MemberSignature is string memberSignature) { members = DocumentationSignatureParser.GetMembersByDocumentationSignature (type, memberSignature, acceptName: true); @@ -1330,7 +1298,8 @@ void MarkEntireAssembly (AssemblyDefinition assembly) MarkCustomAttributes (assembly.MainModule, new DependencyInfo (DependencyKind.AssemblyOrModuleAttribute, assembly.MainModule), null); if (assembly.MainModule.HasExportedTypes) { - // TODO: This needs more work accross all steps + foreach (var exportedType in assembly.MainModule.ExportedTypes) + _context.MarkingHelpers.MarkExportedType (exportedType, assembly.MainModule, new DependencyInfo (DependencyKind.ModuleOfExportedType, assembly.MainModule)); } foreach (TypeDefinition type in assembly.MainModule.Types) @@ -1835,7 +1804,7 @@ protected virtual void MarkTypeConverterLikeDependency (CustomAttribute attribut TypeDefinition tdef = null; switch (attribute.ConstructorArguments[0].Value) { case string s: - tdef = _context.TypeNameResolver.ResolveTypeName (s)?.Resolve (); + tdef = _context.TypeNameResolver.ResolveTypeName (s).Type?.Resolve (); break; case TypeReference type: tdef = type.Resolve (); diff --git a/src/linker/Linker.Steps/SweepStep.cs b/src/linker/Linker.Steps/SweepStep.cs index 209e8c17bd8d..b14fa1fe83af 100644 --- a/src/linker/Linker.Steps/SweepStep.cs +++ b/src/linker/Linker.Steps/SweepStep.cs @@ -531,6 +531,14 @@ protected bool SweepCollectionMetadata (IList list) where T : IMetadataTok protected virtual bool ShouldRemove (T element) where T : IMetadataTokenProvider { + if (typeof (T) == typeof (ExportedType)) { + var typeDef = (element as ExportedType).Resolve (); + if (Context.KeepTypeForwarderOnlyAssemblies) + return !Annotations.IsMarked (typeDef); + + return !Annotations.IsMarked (typeDef) || !Annotations.IsMarked (element); + } + return !Annotations.IsMarked (element); } diff --git a/src/linker/Linker/MarkingHelpers.cs b/src/linker/Linker/MarkingHelpers.cs index b9390247e1a2..c74db126a0bc 100644 --- a/src/linker/Linker/MarkingHelpers.cs +++ b/src/linker/Linker/MarkingHelpers.cs @@ -16,25 +16,7 @@ public void MarkExportedType (ExportedType exportedType, ModuleDefinition module if (!_context.Annotations.MarkProcessed (exportedType, reason)) return; - if (reason.Kind == DependencyKind.ModuleOfExportedType) { - var facadeAssembly = (reason.Source as ModuleDefinition).Assembly; - TypeDefinition resolvedType = exportedType.Resolve (); - while (facadeAssembly.FullName != resolvedType.Module.Assembly.FullName) { - _context.Annotations.Mark (module, new DependencyInfo (DependencyKind.ModuleOfExportedType, exportedType)); - foreach (var assemblyReference in module.AssemblyReferences) { - if (assemblyReference.MetadataToken == exportedType.Scope.MetadataToken) { - facadeAssembly = module.AssemblyResolver.Resolve (assemblyReference); - module = facadeAssembly.MainModule; - break; - } - } - } - - return; - } - - if (_context.KeepTypeForwarderOnlyAssemblies) - _context.Annotations.Mark (module, new DependencyInfo (DependencyKind.ModuleOfExportedType, exportedType)); + _context.Annotations.Mark (module, new DependencyInfo (DependencyKind.ModuleOfExportedType, exportedType)); } } } diff --git a/src/linker/Linker/TypeNameResolver.cs b/src/linker/Linker/TypeNameResolver.cs index 664599e16d9a..7b1233186e6d 100644 --- a/src/linker/Linker/TypeNameResolver.cs +++ b/src/linker/Linker/TypeNameResolver.cs @@ -13,31 +13,35 @@ public TypeNameResolver (LinkContext context) _context = context; } - public TypeReference ResolveTypeName (string typeNameString) + public (AssemblyDefinition Assembly, TypeReference Type) ResolveTypeName (string typeNameString) { if (string.IsNullOrEmpty (typeNameString)) - return null; + return (null, null); TypeName parsedTypeName; try { parsedTypeName = TypeParser.ParseTypeName (typeNameString); } catch (ArgumentException) { - return null; + return (null, null); } catch (System.IO.FileLoadException) { - return null; + return (null, null); } if (parsedTypeName is AssemblyQualifiedTypeName assemblyQualifiedTypeName) { - return ResolveTypeName (null, assemblyQualifiedTypeName); + AssemblyDefinition assemblyDef = _context.TryResolve (assemblyQualifiedTypeName.AssemblyName.Name); + if (assemblyDef == null) + return (null, null); + + return (assemblyDef, ResolveTypeName (assemblyDef, assemblyQualifiedTypeName.TypeName)); } foreach (var assemblyDefinition in _context.GetReferencedAssemblies ()) { var foundType = ResolveTypeName (assemblyDefinition, parsedTypeName); if (foundType != null) - return foundType; + return (assemblyDefinition, foundType); } - return null; + return (null, null); } public TypeReference ResolveTypeName (AssemblyDefinition assembly, string typeNameString) diff --git a/test/Mono.Linker.Tests.Cases/LinkXml/CanPreserveAnExportedType.cs b/test/Mono.Linker.Tests.Cases/LinkXml/CanPreserveAnExportedType.cs index 90017d03ff84..b56f7f0a4e5d 100644 --- a/test/Mono.Linker.Tests.Cases/LinkXml/CanPreserveAnExportedType.cs +++ b/test/Mono.Linker.Tests.Cases/LinkXml/CanPreserveAnExportedType.cs @@ -10,7 +10,6 @@ namespace Mono.Linker.Tests.Cases.LinkXml // Add another assembly in that uses the forwarder just to make things a little more complex [SetupCompileBefore ("Forwarder.dll", new[] { "Dependencies/CanPreserveAnExportedType_Forwarder.cs" }, references: new[] { "Library.dll" })] - [RemovedAssembly ("Forwarder.dll")] [KeptMemberInAssembly ("Library.dll", typeof (CanPreserveAnExportedType_Library), "Field1", "Method()", ".ctor()")] [SetupLinkerDescriptorFile ("CanPreserveAnExportedType.xml")] class CanPreserveAnExportedType diff --git a/test/Mono.Linker.Tests.Cases/LinkXml/CanPreserveExportedTypesUsingRegex.cs b/test/Mono.Linker.Tests.Cases/LinkXml/CanPreserveExportedTypesUsingRegex.cs index 9a54cce19e16..de2202733349 100644 --- a/test/Mono.Linker.Tests.Cases/LinkXml/CanPreserveExportedTypesUsingRegex.cs +++ b/test/Mono.Linker.Tests.Cases/LinkXml/CanPreserveExportedTypesUsingRegex.cs @@ -10,7 +10,6 @@ namespace Mono.Linker.Tests.Cases.LinkXml // Add another assembly in that uses the forwarder just to make things a little more complex [SetupCompileBefore ("Forwarder.dll", new[] { "Dependencies/CanPreserveAnExportedType_Forwarder.cs" }, references: new[] { "Library.dll" })] - [RemovedAssembly ("Forwarder.dll")] [KeptMemberInAssembly ("Library.dll", typeof (CanPreserveAnExportedType_Library), "Field1", "Method()", ".ctor()")] [SetupLinkerDescriptorFile ("CanPreserveExportedTypesUsingRegex.xml")] class CanPreserveExportedTypesUsingRegex diff --git a/test/Mono.Linker.Tests.Cases/LinkXml/UsedNonRequiredExportedTypeIsKept.cs b/test/Mono.Linker.Tests.Cases/LinkXml/UsedNonRequiredExportedTypeIsKept.cs index 44328fb19195..5ff8e00ff8e2 100644 --- a/test/Mono.Linker.Tests.Cases/LinkXml/UsedNonRequiredExportedTypeIsKept.cs +++ b/test/Mono.Linker.Tests.Cases/LinkXml/UsedNonRequiredExportedTypeIsKept.cs @@ -13,7 +13,6 @@ namespace Mono.Linker.Tests.Cases.LinkXml [KeptMemberInAssembly ("libfwd.dll", typeof (UsedNonRequiredExportedTypeIsKept_Used1), "field", ExpectationAssemblyName = "lib.dll")] [KeptMemberInAssembly ("libfwd.dll", typeof (UsedNonRequiredExportedTypeIsKept_Used2), "Method()", ExpectationAssemblyName = "lib.dll")] [KeptMemberInAssembly ("libfwd.dll", typeof (UsedNonRequiredExportedTypeIsKept_Used3), "Method()", ExpectationAssemblyName = "lib.dll")] - [RemovedAssembly ("lib.dll")] public class UsedNonRequiredExportedTypeIsKept { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderIsDynamicallyAccessed.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderIsDynamicallyAccessed.cs index 20aee4f3c971..4000d616f31a 100644 --- a/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderIsDynamicallyAccessed.cs +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderIsDynamicallyAccessed.cs @@ -21,10 +21,9 @@ namespace Mono.Linker.Tests.Cases.TypeForwarding [SetupCompileAfter ("SecondForwarder.dll", new[] { "Dependencies/ForwarderLibrary.cs" }, references: new[] { "Implementation.dll" })] [KeptAssembly ("FirstForwarder.dll")] - [KeptAssembly ("SecondForwarder.dll")] [KeptMemberInAssembly ("Implementation.dll", typeof (ImplementationLibrary), "GetSomeValue()")] + [RemovedAssembly ("SecondForwarder.dll")] [RemovedForwarder ("FirstForwarder.dll", nameof (ImplementationStruct))] - [RemovedForwarder ("SecondForwarder.dll", nameof (ImplementationStruct))] class UsedTransitiveForwarderIsDynamicallyAccessed { static void Main () From 2dd1e3da983a0ad52933a79ff5fa9b2bb58c8dd2 Mon Sep 17 00:00:00 2001 From: Mateo Torres Ruiz Date: Tue, 2 Mar 2021 10:19:17 -0800 Subject: [PATCH 4/6] Add assembly out parameter to ResolveTypeName Add SweepCollectionExportedTypes --- .../ReflectionMethodBodyScanner.cs | 8 ++--- src/linker/Linker.Steps/MarkStep.cs | 6 ++-- src/linker/Linker.Steps/SweepStep.cs | 25 ++++++++++---- src/linker/Linker/TypeNameResolver.cs | 33 +++++++++---------- 4 files changed, 42 insertions(+), 30 deletions(-) diff --git a/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs b/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs index ad800c295016..501314f90402 100644 --- a/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs +++ b/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs @@ -897,7 +897,7 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c } foreach (var typeNameValue in methodParams[0].UniqueValues ()) { if (typeNameValue is KnownStringValue knownStringValue) { - TypeReference foundTypeRef = _context.TypeNameResolver.ResolveTypeName (knownStringValue.Contents).Type; + TypeReference foundTypeRef = _context.TypeNameResolver.ResolveTypeName (knownStringValue.Contents, out _); TypeDefinition foundType = foundTypeRef?.ResolveToMainTypeDefinition (); if (foundType == null) { // Intentionally ignore - it's not wrong for code to call Type.GetType on non-existing name, the code might expect null/exception back. @@ -1804,7 +1804,7 @@ void RequireDynamicallyAccessedMembers (ref ReflectionPatternContext reflectionC } else if (uniqueValue is SystemTypeValue systemTypeValue) { MarkTypeForDynamicallyAccessedMembers (ref reflectionContext, systemTypeValue.TypeRepresented, requiredMemberTypes); } else if (uniqueValue is KnownStringValue knownStringValue) { - (AssemblyDefinition assembly, TypeReference typeRef) = _context.TypeNameResolver.ResolveTypeName (knownStringValue.Contents); + TypeReference typeRef = _context.TypeNameResolver.ResolveTypeName (knownStringValue.Contents, out AssemblyDefinition typeAssembly); TypeDefinition foundType = typeRef?.ResolveToMainTypeDefinition (); if (foundType == null) { // Intentionally ignore - it's not wrong for code to call Type.GetType on non-existing name, the code might expect null/exception back. @@ -1812,8 +1812,8 @@ void RequireDynamicallyAccessedMembers (ref ReflectionPatternContext reflectionC } else { MarkType (ref reflectionContext, typeRef); MarkTypeForDynamicallyAccessedMembers (ref reflectionContext, foundType, requiredMemberTypes); - if (assembly.MainModule.GetMatchingExportedType (foundType, out var exportedType)) - _context.MarkingHelpers.MarkExportedType (exportedType, typeRef.Module, new DependencyInfo (DependencyKind.DynamicallyAccessedMember, foundType)); + if (typeAssembly.MainModule.GetMatchingExportedType (foundType, out var exportedType)) + _context.MarkingHelpers.MarkExportedType (exportedType, typeAssembly.MainModule, new DependencyInfo (DependencyKind.DynamicallyAccessedMember, foundType)); } } else if (uniqueValue == NullValue.Instance) { // Ignore - probably unreachable path as it would fail at runtime anyway. diff --git a/src/linker/Linker.Steps/MarkStep.cs b/src/linker/Linker.Steps/MarkStep.cs index 2780c1547042..b803f910872f 100644 --- a/src/linker/Linker.Steps/MarkStep.cs +++ b/src/linker/Linker.Steps/MarkStep.cs @@ -807,8 +807,10 @@ void MarkDynamicDependency (DynamicDependency dynamicDependency, IMemberDefiniti ModuleDefinition module = assembly.MainModule; if (module.HasExportedTypes) { foreach (var exportedType in module.ExportedTypes) - if (exportedType.FullName == type.FullName) + if (exportedType.Resolve() == type) { _context.MarkingHelpers.MarkExportedType (exportedType, module, new DependencyInfo (DependencyKind.DynamicDependency, type)); + break; + } } IEnumerable members; @@ -1804,7 +1806,7 @@ protected virtual void MarkTypeConverterLikeDependency (CustomAttribute attribut TypeDefinition tdef = null; switch (attribute.ConstructorArguments[0].Value) { case string s: - tdef = _context.TypeNameResolver.ResolveTypeName (s).Type?.Resolve (); + tdef = _context.TypeNameResolver.ResolveTypeName (s, out _)?.Resolve (); break; case TypeReference type: tdef = type.Resolve (); diff --git a/src/linker/Linker.Steps/SweepStep.cs b/src/linker/Linker.Steps/SweepStep.cs index b14fa1fe83af..c2eefe332394 100644 --- a/src/linker/Linker.Steps/SweepStep.cs +++ b/src/linker/Linker.Steps/SweepStep.cs @@ -299,7 +299,7 @@ void SweepResources (AssemblyDefinition assembly) bool SweepTypeForwarders (AssemblyDefinition assembly) { return assembly.MainModule.HasExportedTypes && - SweepCollectionMetadata (assembly.MainModule.ExportedTypes); + SweepCollectionExportedTypes (assembly.MainModule.ExportedTypes); } protected virtual void SweepType (TypeDefinition type) @@ -529,16 +529,27 @@ protected bool SweepCollectionMetadata (IList list) where T : IMetadataTok return removed; } - protected virtual bool ShouldRemove (T element) where T : IMetadataTokenProvider + protected bool SweepCollectionExportedTypes (IList exportedTypes) { - if (typeof (T) == typeof (ExportedType)) { - var typeDef = (element as ExportedType).Resolve (); - if (Context.KeepTypeForwarderOnlyAssemblies) - return !Annotations.IsMarked (typeDef); + bool removed = false; + for (int i = 0; i < exportedTypes.Count; i++) { + var typeDef = exportedTypes[i].Resolve (); + if (Context.KeepTypeForwarderOnlyAssemblies) { + if (!ShouldRemove (typeDef)) + continue; + } else if (!ShouldRemove (typeDef) && !ShouldRemove (exportedTypes[i])) + continue; - return !Annotations.IsMarked (typeDef) || !Annotations.IsMarked (element); + ElementRemoved (exportedTypes[i]); + exportedTypes.RemoveAt (i--); + removed = true; } + return removed; + } + + protected virtual bool ShouldRemove (T element) where T : IMetadataTokenProvider + { return !Annotations.IsMarked (element); } diff --git a/src/linker/Linker/TypeNameResolver.cs b/src/linker/Linker/TypeNameResolver.cs index 7b1233186e6d..6890bf852146 100644 --- a/src/linker/Linker/TypeNameResolver.cs +++ b/src/linker/Linker/TypeNameResolver.cs @@ -13,35 +13,39 @@ public TypeNameResolver (LinkContext context) _context = context; } - public (AssemblyDefinition Assembly, TypeReference Type) ResolveTypeName (string typeNameString) + public TypeReference ResolveTypeName (string typeNameString, out AssemblyDefinition typeAssembly) { + typeAssembly = null; if (string.IsNullOrEmpty (typeNameString)) - return (null, null); + return null; TypeName parsedTypeName; try { parsedTypeName = TypeParser.ParseTypeName (typeNameString); } catch (ArgumentException) { - return (null, null); + return null; } catch (System.IO.FileLoadException) { - return (null, null); + return null; } if (parsedTypeName is AssemblyQualifiedTypeName assemblyQualifiedTypeName) { - AssemblyDefinition assemblyDef = _context.TryResolve (assemblyQualifiedTypeName.AssemblyName.Name); - if (assemblyDef == null) - return (null, null); + typeAssembly = _context.TryResolve (assemblyQualifiedTypeName.AssemblyName.Name); + if (typeAssembly == null) + return null; - return (assemblyDef, ResolveTypeName (assemblyDef, assemblyQualifiedTypeName.TypeName)); + + return ResolveTypeName (typeAssembly, assemblyQualifiedTypeName.TypeName); } foreach (var assemblyDefinition in _context.GetReferencedAssemblies ()) { var foundType = ResolveTypeName (assemblyDefinition, parsedTypeName); - if (foundType != null) - return (assemblyDefinition, foundType); + if (foundType != null) { + typeAssembly = assemblyDefinition; + return foundType; + } } - return (null, null); + return null; } public TypeReference ResolveTypeName (AssemblyDefinition assembly, string typeNameString) @@ -90,12 +94,7 @@ TypeReference ResolveTypeName (AssemblyDefinition assembly, TypeName typeName) }; } - ModuleDefinition module = assembly.MainModule; - TypeDefinition typeDefinition = module.ResolveType (typeName.ToString ()); - if (module.GetMatchingExportedType (typeDefinition, out var exportedType)) { - _context.MarkingHelpers.MarkExportedType (exportedType, module, new DependencyInfo (DependencyKind.ModuleOfExportedType, module)); - } - + TypeDefinition typeDefinition = assembly.MainModule.ResolveType (typeName.ToString ()); return typeDefinition; } } From 511809bb31a782a3fe0ba1a360a8435713c82865 Mon Sep 17 00:00:00 2001 From: Mateo Torres Ruiz Date: Tue, 2 Mar 2021 10:39:08 -0800 Subject: [PATCH 5/6] Fix whitespace --- src/linker/Linker.Steps/MarkStep.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/linker/Linker.Steps/MarkStep.cs b/src/linker/Linker.Steps/MarkStep.cs index b803f910872f..37a9fd7fc68b 100644 --- a/src/linker/Linker.Steps/MarkStep.cs +++ b/src/linker/Linker.Steps/MarkStep.cs @@ -807,7 +807,7 @@ void MarkDynamicDependency (DynamicDependency dynamicDependency, IMemberDefiniti ModuleDefinition module = assembly.MainModule; if (module.HasExportedTypes) { foreach (var exportedType in module.ExportedTypes) - if (exportedType.Resolve() == type) { + if (exportedType.Resolve () == type) { _context.MarkingHelpers.MarkExportedType (exportedType, module, new DependencyInfo (DependencyKind.DynamicDependency, type)); break; } From 10da515f286b308999048de827553a0a6899ab5d Mon Sep 17 00:00:00 2001 From: Mateo Torres Ruiz Date: Thu, 4 Mar 2021 12:23:50 -0800 Subject: [PATCH 6/6] PR Feekdback --- src/linker/Linker.Steps/SweepStep.cs | 21 +++++++++----- .../Linker/ModuleDefinitionExtensions.cs | 2 +- .../LinkXml/CanPreserveAnExportedType.cs | 2 ++ .../CanPreserveExportedTypesUsingRegex.cs | 2 ++ .../UsedNonRequiredExportedTypeIsKept.cs | 17 ++++++----- .../UnusedForwarderWithAssemblyCopy.cs | 28 +++++++++++++++++++ 6 files changed, 57 insertions(+), 15 deletions(-) create mode 100644 test/Mono.Linker.Tests.Cases/TypeForwarding/UnusedForwarderWithAssemblyCopy.cs diff --git a/src/linker/Linker.Steps/SweepStep.cs b/src/linker/Linker.Steps/SweepStep.cs index c2eefe332394..03185dc69519 100644 --- a/src/linker/Linker.Steps/SweepStep.cs +++ b/src/linker/Linker.Steps/SweepStep.cs @@ -185,8 +185,7 @@ protected void ProcessAssemblyAction (AssemblyDefinition assembly) // // when main.exe has unused reference to type in lib.dll // - if (SweepTypeForwarders (assembly)) - Annotations.SetAction (assembly, AssemblyAction.Save); + Annotations.SetAction (assembly, SweepTypeForwarders (assembly) ?? AssemblyAction.Copy); break; @@ -195,8 +194,8 @@ protected void ProcessAssemblyAction (AssemblyDefinition assembly) break; case AssemblyAction.Save: - SweepTypeForwarders (assembly); - + Annotations.SetAction (assembly, SweepTypeForwarders (assembly) ?? AssemblyAction.Save); + // // Save means we need to rewrite the assembly due to removed assembly // references @@ -296,10 +295,18 @@ void SweepResources (AssemblyDefinition assembly) resources.Remove (resource); } - bool SweepTypeForwarders (AssemblyDefinition assembly) + AssemblyAction? SweepTypeForwarders (AssemblyDefinition assembly) { - return assembly.MainModule.HasExportedTypes && - SweepCollectionExportedTypes (assembly.MainModule.ExportedTypes); + if (!assembly.MainModule.HasExportedTypes) + return null; + + // If this is a pure facade and none of the exported types were removed, + // the assembly will be deleted. + var exportedTypes = assembly.MainModule.ExportedTypes; + if (SweepCollectionExportedTypes (exportedTypes) && (exportedTypes.Count == 0) && assembly.MainModule.Types.Count <= 1) + return AssemblyAction.Delete; + + return AssemblyAction.Save; } protected virtual void SweepType (TypeDefinition type) diff --git a/src/linker/Linker/ModuleDefinitionExtensions.cs b/src/linker/Linker/ModuleDefinitionExtensions.cs index 802207601e7c..a79c1efd7e62 100644 --- a/src/linker/Linker/ModuleDefinitionExtensions.cs +++ b/src/linker/Linker/ModuleDefinitionExtensions.cs @@ -18,7 +18,7 @@ public static bool GetMatchingExportedType (this ModuleDefinition module, TypeDe return false; foreach (var et in module.ExportedTypes) - if (et.FullName == typeDefinition.FullName) { + if (et.Resolve () == typeDefinition) { exportedType = et; return true; } diff --git a/test/Mono.Linker.Tests.Cases/LinkXml/CanPreserveAnExportedType.cs b/test/Mono.Linker.Tests.Cases/LinkXml/CanPreserveAnExportedType.cs index b56f7f0a4e5d..4d6796130c7d 100644 --- a/test/Mono.Linker.Tests.Cases/LinkXml/CanPreserveAnExportedType.cs +++ b/test/Mono.Linker.Tests.Cases/LinkXml/CanPreserveAnExportedType.cs @@ -12,6 +12,8 @@ namespace Mono.Linker.Tests.Cases.LinkXml [KeptMemberInAssembly ("Library.dll", typeof (CanPreserveAnExportedType_Library), "Field1", "Method()", ".ctor()")] [SetupLinkerDescriptorFile ("CanPreserveAnExportedType.xml")] + + [KeptTypeInAssembly ("Forwarder.dll", typeof (CanPreserveAnExportedType_Library))] class CanPreserveAnExportedType { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/LinkXml/CanPreserveExportedTypesUsingRegex.cs b/test/Mono.Linker.Tests.Cases/LinkXml/CanPreserveExportedTypesUsingRegex.cs index de2202733349..4bd13f678978 100644 --- a/test/Mono.Linker.Tests.Cases/LinkXml/CanPreserveExportedTypesUsingRegex.cs +++ b/test/Mono.Linker.Tests.Cases/LinkXml/CanPreserveExportedTypesUsingRegex.cs @@ -12,6 +12,8 @@ namespace Mono.Linker.Tests.Cases.LinkXml [KeptMemberInAssembly ("Library.dll", typeof (CanPreserveAnExportedType_Library), "Field1", "Method()", ".ctor()")] [SetupLinkerDescriptorFile ("CanPreserveExportedTypesUsingRegex.xml")] + + [KeptTypeInAssembly ("Forwarder.dll", typeof (CanPreserveAnExportedType_Library))] class CanPreserveExportedTypesUsingRegex { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/LinkXml/UsedNonRequiredExportedTypeIsKept.cs b/test/Mono.Linker.Tests.Cases/LinkXml/UsedNonRequiredExportedTypeIsKept.cs index 5ff8e00ff8e2..2c93982580d0 100644 --- a/test/Mono.Linker.Tests.Cases/LinkXml/UsedNonRequiredExportedTypeIsKept.cs +++ b/test/Mono.Linker.Tests.Cases/LinkXml/UsedNonRequiredExportedTypeIsKept.cs @@ -5,14 +5,17 @@ namespace Mono.Linker.Tests.Cases.LinkXml { [SetupLinkerDescriptorFile ("UsedNonRequiredExportedTypeIsKept.xml")] - [SetupCompileBefore ("lib.dll", new[] { "Dependencies/UsedNonRequiredExportedTypeIsKept_lib.cs" })] - [SetupCompileAfter ("libfwd.dll", new[] { "Dependencies/UsedNonRequiredExportedTypeIsKept_lib.cs" })] - [SetupCompileAfter ("lib.dll", new[] { "Dependencies/UsedNonRequiredExportedTypeIsKept_fwd.cs" }, references: new[] { "libfwd.dll" })] + [SetupCompileBefore ("libfwd.dll", new[] { "Dependencies/UsedNonRequiredExportedTypeIsKept_lib.cs" })] + [SetupCompileAfter ("lib.dll", new[] { "Dependencies/UsedNonRequiredExportedTypeIsKept_lib.cs" })] + [SetupCompileAfter ("libfwd.dll", new[] { "Dependencies/UsedNonRequiredExportedTypeIsKept_fwd.cs" }, references: new[] { "lib.dll" })] + [SetupLinkerAction ("copy", "libfwd")] - [KeptAssembly ("libfwd.dll")] - [KeptMemberInAssembly ("libfwd.dll", typeof (UsedNonRequiredExportedTypeIsKept_Used1), "field", ExpectationAssemblyName = "lib.dll")] - [KeptMemberInAssembly ("libfwd.dll", typeof (UsedNonRequiredExportedTypeIsKept_Used2), "Method()", ExpectationAssemblyName = "lib.dll")] - [KeptMemberInAssembly ("libfwd.dll", typeof (UsedNonRequiredExportedTypeIsKept_Used3), "Method()", ExpectationAssemblyName = "lib.dll")] + [KeptMemberInAssembly ("lib.dll", typeof (UsedNonRequiredExportedTypeIsKept_Used1), "field", ExpectationAssemblyName = "libfwd.dll")] + [KeptMemberInAssembly ("lib.dll", typeof (UsedNonRequiredExportedTypeIsKept_Used2), "Method()", ExpectationAssemblyName = "libfwd.dll")] + [KeptMemberInAssembly ("lib.dll", typeof (UsedNonRequiredExportedTypeIsKept_Used3), "Method()", ExpectationAssemblyName = "libfwd.dll")] + [KeptTypeInAssembly ("libfwd.dll", typeof (UsedNonRequiredExportedTypeIsKept_Used1))] + [KeptTypeInAssembly ("libfwd.dll", typeof (UsedNonRequiredExportedTypeIsKept_Used2))] + [KeptTypeInAssembly ("libfwd.dll", typeof (UsedNonRequiredExportedTypeIsKept_Used3))] public class UsedNonRequiredExportedTypeIsKept { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/UnusedForwarderWithAssemblyCopy.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/UnusedForwarderWithAssemblyCopy.cs new file mode 100644 index 000000000000..8aacfc6f1b7d --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/UnusedForwarderWithAssemblyCopy.cs @@ -0,0 +1,28 @@ +// 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. + +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 +{ + [SetupLinkerDefaultAction ("link")] + [SetupCompileBefore ("Forwarder.dll", new[] { "Dependencies/ReferenceImplementationLibrary.cs" }, defines: new[] { "INCLUDE_REFERENCE_IMPL" })] + + [SetupCompileAfter ("Implementation.dll", new[] { "Dependencies/ImplementationLibrary.cs" })] + [SetupCompileAfter ("Forwarder.dll", new[] { "Dependencies/ForwarderLibrary.cs" }, references: new[] { "Implementation.dll" })] + + [SetupLinkerAction ("copy", "Forwarder")] + + [RemovedAssembly ("Forwarder.dll")] + [RemovedAssembly ("Implementation.dll")] + + public class UnusedForwarderWithAssemblyCopy + { + static void Main () + { + } + } +}