From 2674d7d0c36159aa1aec2fb0a53fffecf50e5780 Mon Sep 17 00:00:00 2001 From: Mateo Torres-Ruiz Date: Tue, 6 Apr 2021 12:59:55 -0700 Subject: [PATCH] Copy action behavior (#1941) * Stop rewriting scopes for copy assemblies with removed references * Keep exported types in copy assemblies Mark dynamically accessed forwarders Add tests * Transitive forwarders are linked * Keep copyused behavior * Tests * Mark type if it's exported * PR feedback * Transitively mark forwarders when a facade is dynamically accessed and has copy action * Transitively mark forwarders * Fix formatting * Update MarkExportedType * Keep mscorlib assert * Mark forwarders when there's a type ref to an exported type in a copy assembly * Update DependencyKind * Keep forwarders when member ref is resolved from a copy assembly * Add more tests, mark forwarded CAs * Mark forwarded interfaces * Simplify logic for typerefs * Clean * Keep ProcessInternalsVisibleAttributes in process while-loop * Fix whitespace formatting * Remove unused param * Feedback * Whitespace formatting * Remove unnecessary assembly * Add IL2104 warning * Remove unnecessary marking * Update comment * Remove ExportedTypeExtensions * Remove formatDiagnostics and revert cecil subm change * Remove warning * Comment out check for removed warning * Update more of existing calls * Reproduce attribute issue * Update scopes before sweeping forwarders * Remove my enum from compilation * Fix formatting * Keep same behavior for scopes in copyused assemblies Co-authored-by: Marek Safar Co-authored-by: Sven Boemer --- .../ReflectionMethodBodyScanner.cs | 6 +- src/linker/Linker.Steps/DescriptorMarker.cs | 3 + .../Linker.Steps/LinkAttributesParser.cs | 4 +- src/linker/Linker.Steps/MarkStep.cs | 81 ++++++++----------- src/linker/Linker.Steps/OutputStep.cs | 26 +----- src/linker/Linker.Steps/SweepStep.cs | 54 ++++++------- src/linker/Linker/MarkingHelpers.cs | 29 ++++++- .../Linker/ModuleDefinitionExtensions.cs | 15 ++++ src/linker/Linker/TypeNameResolver.cs | 13 ++- .../LinkXml/CanPreserveAnExportedType.cs | 2 +- .../CanPreserveExportedTypesUsingRegex.cs | 2 +- .../UsedNonRequiredExportedTypeIsKept.cs | 18 +++-- .../UsedNonRequiredExportedTypeIsKept.xml | 2 +- ...NonRequiredExportedTypeIsKeptWhenRooted.cs | 14 ++-- .../Mono.Linker.Tests.Cases.csproj | 6 +- .../AssemblyOnlyUsedByUsingWithCsc.cs | 7 +- ...lyOnlyUsedByUsingWithCscWithKeepFacades.cs | 7 +- .../ReferenceWithEmbeddedPdbCopyAction.cs | 3 +- .../AttributeArgumentForwarded.cs | 4 - ...ttributeArgumentForwardedWithCopyAction.cs | 2 +- .../AttributeEnumArgumentForwarded.cs | 32 ++++++++ .../TypeForwarding/AttributesScopeUpdated.cs | 2 +- .../Dependencies/AttributeWithEnumArgument.cs | 16 ++++ .../Dependencies/ForwarderLibrary.cs | 3 + .../Dependencies/ImplementationLibrary.cs | 24 ++++++ .../TypeForwarding/Dependencies/MyEnum.cs | 13 +++ .../Dependencies/MyEnumForwarder.cs | 11 +++ .../ReferenceImplementationLibrary.cs | 24 ++++++ .../TypeForwarding/SecurityAttributeScope.cs | 5 +- .../UnusedForwarderWithAssemblyCopyIsKept.cs | 28 +++++++ .../UsedAndUnusedForwarderWithAssemblyCopy.cs | 8 +- ...rInCopyAssemblyKeptByPreserveDependency.cs | 33 ++++++++ ...InCopyAssemblyKeptByUsedCustomAttribute.cs | 32 ++++++++ ...dForwarderInCopyAssemblyKeptByUsedField.cs | 31 +++++++ ...warderInCopyAssemblyKeptByUsedInterface.cs | 32 ++++++++ ...ForwarderInCopyAssemblyKeptByUsedMethod.cs | 31 +++++++ ...arderInCopyAssemblyKeptByUsedNestedType.cs | 31 +++++++ ...rwarderInCopyAssemblyKeptByUsedProperty.cs | 32 ++++++++ ...nCopyAssemblyKeptByUsedTypeAsGenericArg.cs | 31 +++++++ ...DynamicallyAccessedWithAssemblyCopyUsed.cs | 38 +++++++++ ...arderWithAssemblyCopyUsedAndFacadesKept.cs | 1 + ...rderInCopyAssemblyIsDynamicallyAccessed.cs | 41 ++++++++++ ...InCopyUsedAssemblyIsDynamicallyAccessed.cs | 43 ++++++++++ ...ransitiveForwarderIsDynamicallyAccessed.cs | 41 ++++++++++ ...tiveForwarderIsResolvedAndFacadeRemoved.cs | 31 +++++++ ...sResolvedAndFacadeRemovedInCopyAssembly.cs | 30 +++++++ .../TestCasesRunner/ExpectationsProvider.cs | 6 +- .../TestCasesRunner/ResultChecker.cs | 57 +++++++++++-- 48 files changed, 848 insertions(+), 157 deletions(-) create mode 100644 test/Mono.Linker.Tests.Cases/TypeForwarding/AttributeEnumArgumentForwarded.cs create mode 100644 test/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/AttributeWithEnumArgument.cs create mode 100644 test/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/MyEnum.cs create mode 100644 test/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/MyEnumForwarder.cs create mode 100644 test/Mono.Linker.Tests.Cases/TypeForwarding/UnusedForwarderWithAssemblyCopyIsKept.cs create mode 100644 test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByPreserveDependency.cs create mode 100644 test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByUsedCustomAttribute.cs create mode 100644 test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByUsedField.cs create mode 100644 test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByUsedInterface.cs create mode 100644 test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByUsedMethod.cs create mode 100644 test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByUsedNestedType.cs create mode 100644 test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByUsedProperty.cs create mode 100644 test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByUsedTypeAsGenericArg.cs create mode 100644 test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderIsDynamicallyAccessedWithAssemblyCopyUsed.cs create mode 100644 test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderInCopyAssemblyIsDynamicallyAccessed.cs create mode 100644 test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderInCopyUsedAssemblyIsDynamicallyAccessed.cs create mode 100644 test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderIsDynamicallyAccessed.cs create mode 100644 test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderIsResolvedAndFacadeRemoved.cs create mode 100644 test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderIsResolvedAndFacadeRemovedInCopyAssembly.cs diff --git a/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs b/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs index b73865d7f3de..5eb428ed3961 100644 --- a/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs +++ b/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs @@ -966,7 +966,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, out AssemblyDefinition typeAssembly); 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. @@ -974,6 +974,7 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c } else { reflectionContext.RecordRecognizedPattern (foundType, () => _markStep.MarkTypeVisibleToReflection (foundTypeRef, new DependencyInfo (DependencyKind.AccessedViaReflection, callingMethodDefinition), callingMethodDefinition)); methodReturnValue = MergePointValue.MergeValues (methodReturnValue, new SystemTypeValue (foundType)); + _context.MarkingHelpers.MarkMatchingExportedType (foundType, typeAssembly, new DependencyInfo (DependencyKind.AccessedViaReflection, foundType)); } } else if (typeNameValue == NullValue.Instance) { reflectionContext.RecordHandledPattern (); @@ -1977,7 +1978,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); + 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. @@ -1985,6 +1986,7 @@ void RequireDynamicallyAccessedMembers (ref ReflectionPatternContext reflectionC } else { MarkType (ref reflectionContext, typeRef); MarkTypeForDynamicallyAccessedMembers (ref reflectionContext, foundType, requiredMemberTypes); + _context.MarkingHelpers.MarkMatchingExportedType (foundType, typeAssembly, 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 e9479dddc0d6..5915f974efd6 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.XmlDescriptor, assembly.MainModule)); } else { ProcessTypes (assembly, nav, warnOnUnresolvedTypes); ProcessNamespaces (assembly, nav); diff --git a/src/linker/Linker.Steps/LinkAttributesParser.cs b/src/linker/Linker.Steps/LinkAttributesParser.cs index 5a0551b9c6cd..5d551671910e 100644 --- a/src/linker/Linker.Steps/LinkAttributesParser.cs +++ b/src/linker/Linker.Steps/LinkAttributesParser.cs @@ -264,7 +264,7 @@ CustomAttributeArgument[] ReadCustomAttributeArguments (XPathNodeIterator iterat if (!typeref.IsTypeOf ("System", "Type")) goto default; - TypeReference type = _context.TypeNameResolver.ResolveTypeName (svalue); + TypeReference type = _context.TypeNameResolver.ResolveTypeName (svalue, out _); if (type == null) { _context.LogError ($"Could not resolve custom attribute type value '{svalue}'", 1044, _xmlDocumentLocation); return null; @@ -284,7 +284,7 @@ TypeReference ResolveArgumentType (XPathNodeIterator iterator) if (string.IsNullOrEmpty (typeName)) typeName = "System.String"; - TypeReference typeref = _context.TypeNameResolver.ResolveTypeName (typeName); + TypeReference typeref = _context.TypeNameResolver.ResolveTypeName (typeName, out _); if (typeref == null) { _context.LogError ($"The type '{typeName}' used with attribute value '{iterator.Current.Value}' could not be found", 1041, _xmlDocumentLocation); return null; diff --git a/src/linker/Linker.Steps/MarkStep.cs b/src/linker/Linker.Steps/MarkStep.cs index cb9fba61df56..d48384f2beed 100644 --- a/src/linker/Linker.Steps/MarkStep.cs +++ b/src/linker/Linker.Steps/MarkStep.cs @@ -189,6 +189,7 @@ public MarkStep () } public AnnotationStore Annotations => _context.Annotations; + public MarkingHelpers MarkingHelpers => _context.MarkingHelpers; public Tracer Tracer => _context.Tracer; public virtual void Process (LinkContext context) @@ -365,40 +366,12 @@ private void MarkEntireTypeInternal (TypeDefinition type, bool includeBaseTypes, void Process () { - while (ProcessPrimaryQueue () || ProcessMarkedPending () || ProcessLazyAttributes () || ProcessLateMarkedAttributes () || MarkFullyPreservedAssemblies () || ProcessInternalsVisibleAttributes ()) { - - // 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 () || + ProcessInternalsVisibleAttributes ()) ; ProcessPendingTypeChecks (); } @@ -858,6 +831,8 @@ void MarkDynamicDependency (DynamicDependency dynamicDependency, IMemberDefiniti _context.LogWarning ($"Unresolved type '{typeName}' in DynamicDependencyAttribute", 2036, context); return; } + + MarkingHelpers.MarkMatchingExportedType (type, assembly, new DependencyInfo (DependencyKind.DynamicDependency, type)); } else if (dynamicDependency.Type is TypeReference typeReference) { type = typeReference.Resolve (); if (type == null) { @@ -947,14 +922,20 @@ protected virtual void MarkUserDependency (IMemberDefinition context, CustomAttr assembly = null; } - TypeDefinition td; + TypeDefinition td = null; if (args.Count >= 2 && args[1].Value is string typeName) { - td = _context.TypeNameResolver.ResolveTypeName (assembly ?? (context as MemberReference).Module.Assembly, typeName)?.Resolve (); + AssemblyDefinition assemblyDef = assembly ?? (context as MemberReference).Module.Assembly; + TypeReference tr = _context.TypeNameResolver.ResolveTypeName (assemblyDef, typeName); + if (tr != null) + td = tr.Resolve (); + if (td == null) { _context.LogWarning ( $"Could not resolve dependency type '{typeName}' specified in a `PreserveDependency` attribute", 2004, context); return; } + + MarkingHelpers.MarkMatchingExportedType (td, assemblyDef, new DependencyInfo (DependencyKind.PreservedDependency, ca)); } else { td = context.DeclaringType.Resolve (); } @@ -1357,15 +1338,20 @@ void MarkEntireAssembly (AssemblyDefinition assembly) { Debug.Assert (Annotations.IsProcessed (assembly)); + ModuleDefinition module = assembly.MainModule; MarkCustomAttributes (assembly, new DependencyInfo (DependencyKind.AssemblyOrModuleAttribute, assembly), null); - MarkCustomAttributes (assembly.MainModule, new DependencyInfo (DependencyKind.AssemblyOrModuleAttribute, assembly.MainModule), null); + MarkCustomAttributes (module, new DependencyInfo (DependencyKind.AssemblyOrModuleAttribute, module), null); - if (assembly.MainModule.HasExportedTypes) { - // TODO: This needs more work accross all steps + foreach (TypeDefinition type in module.Types) + MarkEntireType (type, includeBaseTypes: false, includeInterfaceTypes: false, new DependencyInfo (DependencyKind.TypeInAssembly, assembly), null); + + foreach (ExportedType exportedType in module.ExportedTypes) { + MarkingHelpers.MarkExportedType (exportedType, module, new DependencyInfo (DependencyKind.ExportedType, assembly)); + MarkingHelpers.MarkForwardedScope (new TypeReference (exportedType.Namespace, exportedType.Name, module, exportedType.Scope)); } - foreach (TypeDefinition type in assembly.MainModule.Types) - MarkEntireType (type, includeBaseTypes: false, includeInterfaceTypes: false, new DependencyInfo (DependencyKind.TypeInAssembly, assembly), null); + foreach (TypeReference typeReference in module.GetTypeReferences ()) + MarkingHelpers.MarkForwardedScope (typeReference); } void ProcessModuleType (AssemblyDefinition assembly) @@ -1870,21 +1856,24 @@ protected virtual void MarkTypeConverterLikeDependency (CustomAttribute attribut if (args.Count < 1) return; - TypeDefinition tdef = null; + TypeDefinition typeDefinition = null; switch (attribute.ConstructorArguments[0].Value) { case string s: - tdef = _context.TypeNameResolver.ResolveTypeName (s)?.Resolve (); + typeDefinition = _context.TypeNameResolver.ResolveTypeName (s, out AssemblyDefinition assemblyDefinition)?.Resolve (); + if (typeDefinition != null) + MarkingHelpers.MarkMatchingExportedType (typeDefinition, assemblyDefinition, new DependencyInfo (DependencyKind.CustomAttribute, provider)); + break; case TypeReference type: - tdef = type.Resolve (); + typeDefinition = type.Resolve (); break; } - if (tdef == null) + if (typeDefinition == null) return; Tracer.AddDirectDependency (attribute, new DependencyInfo (DependencyKind.CustomAttribute, provider), marked: false); - MarkMethodsIf (tdef.Methods, predicate, new DependencyInfo (DependencyKind.ReferencedBySpecialAttribute, attribute), sourceLocationMember); + MarkMethodsIf (typeDefinition.Methods, predicate, new DependencyInfo (DependencyKind.ReferencedBySpecialAttribute, attribute), sourceLocationMember); } void MarkTypeWithDebuggerDisplayAttribute (TypeDefinition type, CustomAttribute attribute) diff --git a/src/linker/Linker.Steps/OutputStep.cs b/src/linker/Linker.Steps/OutputStep.cs index f56b2c750118..5ac66ab8301e 100644 --- a/src/linker/Linker.Steps/OutputStep.cs +++ b/src/linker/Linker.Steps/OutputStep.cs @@ -271,42 +271,24 @@ FileInfo GetOriginalAssemblyFileInfo (AssemblyDefinition assembly) protected virtual void CopyAssembly (AssemblyDefinition assembly, string directory) { - // Special case. When an assembly has embedded pdbs, link symbols is not enabled, and the assembly's action is copy, - // we want to match the behavior of assemblies with the other symbol types and end up with an assembly that does not have symbols. - // In order to do that, we can't simply copy files. We need to write the assembly without symbols - if (assembly.MainModule.HasSymbols && !Context.LinkSymbols && assembly.MainModule.SymbolReader is EmbeddedPortablePdbReader) { - WriteAssembly (assembly, directory, new WriterParameters ()); - return; - } - FileInfo fi = GetOriginalAssemblyFileInfo (assembly); string target = Path.GetFullPath (Path.Combine (directory, fi.Name)); string source = fi.FullName; + if (source == target) return; - CopyFileAndRemoveReadOnly (source, target); - + File.Copy (source, target, true); if (!Context.LinkSymbols) return; var mdb = source + ".mdb"; if (File.Exists (mdb)) - CopyFileAndRemoveReadOnly (mdb, target + ".mdb"); + File.Copy (mdb, target + ".mdb", true); var pdb = Path.ChangeExtension (source, "pdb"); if (File.Exists (pdb)) - CopyFileAndRemoveReadOnly (pdb, Path.ChangeExtension (target, "pdb")); - } - - static void CopyFileAndRemoveReadOnly (string src, string dest) - { - File.Copy (src, dest, true); - - System.IO.FileAttributes attrs = File.GetAttributes (dest); - - if ((attrs & System.IO.FileAttributes.ReadOnly) == System.IO.FileAttributes.ReadOnly) - File.SetAttributes (dest, attrs & ~System.IO.FileAttributes.ReadOnly); + File.Copy (pdb, Path.ChangeExtension (target, "pdb"), true); } protected virtual string GetAssemblyFileName (AssemblyDefinition assembly, string directory) diff --git a/src/linker/Linker.Steps/SweepStep.cs b/src/linker/Linker.Steps/SweepStep.cs index 209e8c17bd8d..2a07acbee0a5 100644 --- a/src/linker/Linker.Steps/SweepStep.cs +++ b/src/linker/Linker.Steps/SweepStep.cs @@ -63,6 +63,16 @@ protected override void Process () foreach (var assembly in assemblies) UpdateAssemblyReferencesToRemovedAssemblies (assembly); + // Update scopes before removing any type forwarder. + foreach (var assembly in assemblies) { + switch (Annotations.GetAction (assembly)) { + case AssemblyAction.Link: + case AssemblyAction.Save: + SweepAssemblyReferences (assembly); + break; + } + } + foreach (var assembly in assemblies) ProcessAssemblyAction (assembly); @@ -97,13 +107,13 @@ void UpdateAssemblyReferencesToRemovedAssemblies (AssemblyDefinition assembly) { var action = Annotations.GetAction (assembly); switch (action) { - case AssemblyAction.Skip: + case AssemblyAction.Copy: case AssemblyAction.Delete: case AssemblyAction.Link: case AssemblyAction.Save: + case AssemblyAction.Skip: return; - case AssemblyAction.Copy: case AssemblyAction.CopyUsed: case AssemblyAction.AddBypassNGen: case AssemblyAction.AddBypassNGenUsed: @@ -121,7 +131,6 @@ void UpdateAssemblyReferencesToRemovedAssemblies (AssemblyDefinition assembly) switch (action) { case AssemblyAction.CopyUsed: - case AssemblyAction.Copy: // // Assembly has a reference to another assembly which has been fully removed. This can // happen when for example the reference assembly is 'copy-used' and it's not needed. @@ -172,22 +181,14 @@ protected void ProcessAssemblyAction (AssemblyDefinition assembly) break; case AssemblyAction.CopyUsed: - Annotations.SetAction (assembly, AssemblyAction.Copy); - goto case AssemblyAction.Copy; + AssemblyAction assemblyAction = AssemblyAction.Copy; + if (!Context.KeepTypeForwarderOnlyAssemblies && SweepTypeForwarders (assembly)) + assemblyAction = AssemblyAction.Save; - case AssemblyAction.Copy: - // - // Facade assemblies can have unused forwarders pointing to - // removed type (when facades are kept) - // - // main.exe -> facade.dll -> lib.dll - // link | copy | link - // - // when main.exe has unused reference to type in lib.dll - // - if (SweepTypeForwarders (assembly)) - Annotations.SetAction (assembly, AssemblyAction.Save); + Annotations.SetAction (assembly, assemblyAction); + break; + case AssemblyAction.Copy: break; case AssemblyAction.Link: @@ -196,12 +197,6 @@ protected void ProcessAssemblyAction (AssemblyDefinition assembly) case AssemblyAction.Save: SweepTypeForwarders (assembly); - - // - // Save means we need to rewrite the assembly due to removed assembly - // references - // - SweepAssemblyReferences (assembly); break; } } @@ -210,11 +205,13 @@ protected virtual void SweepAssembly (AssemblyDefinition assembly) { var types = new List (); ModuleDefinition main = assembly.MainModule; + bool updateScopes = false; foreach (TypeDefinition type in main.Types) { if (!ShouldRemove (type)) { SweepType (type); types.Add (type); + updateScopes = true; continue; } @@ -230,24 +227,23 @@ protected virtual void SweepAssembly (AssemblyDefinition assembly) main.Types.Add (type); SweepResources (assembly); - SweepCustomAttributes (assembly); + updateScopes |= SweepCustomAttributes (assembly); foreach (var module in assembly.Modules) - SweepCustomAttributes (module); + updateScopes |= SweepCustomAttributes (module); // // MainModule module references are used by pinvoke // if (main.HasModuleReferences) - SweepCollectionMetadata (main.ModuleReferences); + updateScopes |= SweepCollectionMetadata (main.ModuleReferences); if (main.EntryPoint != null && !Annotations.IsMarked (main.EntryPoint)) { main.EntryPoint = null; } - SweepTypeForwarders (assembly); - - SweepAssemblyReferences (assembly); + if (SweepTypeForwarders (assembly) || updateScopes) + SweepAssemblyReferences (assembly); } static void SweepAssemblyReferences (AssemblyDefinition assembly) diff --git a/src/linker/Linker/MarkingHelpers.cs b/src/linker/Linker/MarkingHelpers.cs index f59ffd162571..ca2a9b36c73c 100644 --- a/src/linker/Linker/MarkingHelpers.cs +++ b/src/linker/Linker/MarkingHelpers.cs @@ -11,12 +11,33 @@ public MarkingHelpers (LinkContext context) _context = context; } - public void MarkExportedType (ExportedType type, ModuleDefinition module, in DependencyInfo reason) + public void MarkMatchingExportedType (TypeDefinition typeToMatch, AssemblyDefinition assembly, in DependencyInfo reason) { - if (!_context.Annotations.MarkProcessed (type, reason)) + if (typeToMatch == null || assembly == null) return; - if (_context.KeepTypeForwarderOnlyAssemblies) - _context.Annotations.Mark (module, new DependencyInfo (DependencyKind.ModuleOfExportedType, type)); + + if (assembly.MainModule.GetMatchingExportedType (typeToMatch, out var exportedType)) + MarkExportedType (exportedType, assembly.MainModule, reason); + } + + public void MarkExportedType (ExportedType exportedType, ModuleDefinition module, in DependencyInfo reason) + { + if (!_context.Annotations.MarkProcessed (exportedType, reason)) + return; + + _context.Annotations.Mark (module, reason); + } + + public void MarkForwardedScope (TypeReference typeReference) + { + if (typeReference == null) + return; + + if (typeReference.Scope is AssemblyNameReference) { + var assembly = _context.Resolve (typeReference.Scope); + if (assembly != null && assembly.MainModule.GetMatchingExportedType (typeReference.Resolve (), out var exportedType)) + MarkExportedType (exportedType, assembly.MainModule, new DependencyInfo (DependencyKind.ExportedType, typeReference)); + } } } } diff --git a/src/linker/Linker/ModuleDefinitionExtensions.cs b/src/linker/Linker/ModuleDefinitionExtensions.cs index f846c96f40e3..a79c1efd7e62 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.Resolve () == typeDefinition) { + 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..1dac10ef87ca 100644 --- a/src/linker/Linker/TypeNameResolver.cs +++ b/src/linker/Linker/TypeNameResolver.cs @@ -13,8 +13,9 @@ public TypeNameResolver (LinkContext context) _context = context; } - public TypeReference ResolveTypeName (string typeNameString) + public TypeReference ResolveTypeName (string typeNameString, out AssemblyDefinition typeAssembly) { + typeAssembly = null; if (string.IsNullOrEmpty (typeNameString)) return null; @@ -28,13 +29,19 @@ public TypeReference ResolveTypeName (string typeNameString) } if (parsedTypeName is AssemblyQualifiedTypeName assemblyQualifiedTypeName) { - return ResolveTypeName (null, assemblyQualifiedTypeName); + typeAssembly = _context.TryResolve (assemblyQualifiedTypeName.AssemblyName.Name); + if (typeAssembly == null) + return null; + + return ResolveTypeName (typeAssembly, assemblyQualifiedTypeName.TypeName); } foreach (var assemblyDefinition in _context.GetReferencedAssemblies ()) { var foundType = ResolveTypeName (assemblyDefinition, parsedTypeName); - if (foundType != null) + if (foundType != null) { + typeAssembly = assemblyDefinition; return foundType; + } } return null; diff --git a/test/Mono.Linker.Tests.Cases/LinkXml/CanPreserveAnExportedType.cs b/test/Mono.Linker.Tests.Cases/LinkXml/CanPreserveAnExportedType.cs index 90017d03ff84..b083b446fb6c 100644 --- a/test/Mono.Linker.Tests.Cases/LinkXml/CanPreserveAnExportedType.cs +++ b/test/Mono.Linker.Tests.Cases/LinkXml/CanPreserveAnExportedType.cs @@ -10,9 +10,9 @@ 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")] + [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 9a54cce19e16..d84f952a0d32 100644 --- a/test/Mono.Linker.Tests.Cases/LinkXml/CanPreserveExportedTypesUsingRegex.cs +++ b/test/Mono.Linker.Tests.Cases/LinkXml/CanPreserveExportedTypesUsingRegex.cs @@ -10,9 +10,9 @@ 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")] + [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 44328fb19195..c990ddbfa462 100644 --- a/test/Mono.Linker.Tests.Cases/LinkXml/UsedNonRequiredExportedTypeIsKept.cs +++ b/test/Mono.Linker.Tests.Cases/LinkXml/UsedNonRequiredExportedTypeIsKept.cs @@ -5,15 +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" })] + + // Note that forwarders which are referenced from within a descriptor XML file are kept -- any exported type referenced through a type + // name string should be kept. + [KeptMemberInAssembly ("libfwd.dll", typeof (UsedNonRequiredExportedTypeIsKept_Used1), "field")] + [KeptMemberInAssembly ("libfwd.dll", typeof (UsedNonRequiredExportedTypeIsKept_Used2), "Method()")] + [KeptMemberInAssembly ("libfwd.dll", typeof (UsedNonRequiredExportedTypeIsKept_Used3), "Method()")] + [KeptAssembly ("lib.dll")] - [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")] - [RemovedAssembly ("lib.dll")] public class UsedNonRequiredExportedTypeIsKept { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/LinkXml/UsedNonRequiredExportedTypeIsKept.xml b/test/Mono.Linker.Tests.Cases/LinkXml/UsedNonRequiredExportedTypeIsKept.xml index 223744d49b3c..659c05db584c 100644 --- a/test/Mono.Linker.Tests.Cases/LinkXml/UsedNonRequiredExportedTypeIsKept.xml +++ b/test/Mono.Linker.Tests.Cases/LinkXml/UsedNonRequiredExportedTypeIsKept.xml @@ -1,5 +1,5 @@ - + diff --git a/test/Mono.Linker.Tests.Cases/LinkXml/UsedNonRequiredExportedTypeIsKeptWhenRooted.cs b/test/Mono.Linker.Tests.Cases/LinkXml/UsedNonRequiredExportedTypeIsKeptWhenRooted.cs index eec9e5bfc4d6..96630532a60b 100644 --- a/test/Mono.Linker.Tests.Cases/LinkXml/UsedNonRequiredExportedTypeIsKeptWhenRooted.cs +++ b/test/Mono.Linker.Tests.Cases/LinkXml/UsedNonRequiredExportedTypeIsKeptWhenRooted.cs @@ -4,16 +4,16 @@ namespace Mono.Linker.Tests.Cases.LinkXml { [SetupLinkerDescriptorFile ("UsedNonRequiredExportedTypeIsKeptWhenRooted.xml")] - [SetupLinkerArgument ("-a", "lib.dll", "visible")] + [SetupLinkerArgument ("-a", "libfwd.dll", "visible")] - [SetupCompileBefore ("lib.dll", new[] { "Dependencies/UsedNonRequiredExportedTypeIsKeptWhenRooted_lib.cs" })] - [SetupCompileAfter ("libfwd.dll", new[] { "Dependencies/UsedNonRequiredExportedTypeIsKeptWhenRooted_lib.cs" })] - [SetupCompileAfter ("lib.dll", new[] { "Dependencies/UsedNonRequiredExportedTypeIsKeptWhenRooted_fwd.cs" }, references: new[] { "libfwd.dll" })] + [SetupCompileBefore ("libfwd.dll", new[] { "Dependencies/UsedNonRequiredExportedTypeIsKeptWhenRooted_lib.cs" })] + [SetupCompileAfter ("lib.dll", new[] { "Dependencies/UsedNonRequiredExportedTypeIsKeptWhenRooted_lib.cs" })] + [SetupCompileAfter ("libfwd.dll", new[] { "Dependencies/UsedNonRequiredExportedTypeIsKeptWhenRooted_fwd.cs" }, references: new[] { "lib.dll" })] - [KeptAssembly ("libfwd.dll")] [KeptAssembly ("lib.dll")] - [KeptMemberInAssembly ("libfwd.dll", typeof (UsedNonRequiredExportedTypeIsKeptWhenRooted_Used), "field", ExpectationAssemblyName = "lib.dll")] - [KeptMemberInAssembly ("libfwd.dll", typeof (UsedNonRequiredExportedTypeIsKeptWhenRooted_Used), "Method()", ExpectationAssemblyName = "lib.dll")] + [KeptAssembly ("libfwd.dll")] + [KeptMemberInAssembly ("lib.dll", typeof (UsedNonRequiredExportedTypeIsKeptWhenRooted_Used), "field", ExpectationAssemblyName = "libfwd.dll")] + [KeptMemberInAssembly ("lib.dll", typeof (UsedNonRequiredExportedTypeIsKeptWhenRooted_Used), "Method()", ExpectationAssemblyName = "libfwd.dll")] public class UsedNonRequiredExportedTypeIsKeptWhenRooted { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/Mono.Linker.Tests.Cases.csproj b/test/Mono.Linker.Tests.Cases/Mono.Linker.Tests.Cases.csproj index 3f6e98d6799e..8e3b8286ef5b 100644 --- a/test/Mono.Linker.Tests.Cases/Mono.Linker.Tests.Cases.csproj +++ b/test/Mono.Linker.Tests.Cases/Mono.Linker.Tests.Cases.csproj @@ -14,14 +14,15 @@ + - + - + @@ -33,6 +34,7 @@ + diff --git a/test/Mono.Linker.Tests.Cases/References/AssemblyOnlyUsedByUsingWithCsc.cs b/test/Mono.Linker.Tests.Cases/References/AssemblyOnlyUsedByUsingWithCsc.cs index 48b87d074917..7be38642a9ae 100644 --- a/test/Mono.Linker.Tests.Cases/References/AssemblyOnlyUsedByUsingWithCsc.cs +++ b/test/Mono.Linker.Tests.Cases/References/AssemblyOnlyUsedByUsingWithCsc.cs @@ -1,4 +1,5 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Helpers; using Mono.Linker.Tests.Cases.Expectations.Metadata; using Mono.Linker.Tests.Cases.References.Dependencies; @@ -20,11 +21,7 @@ namespace Mono.Linker.Tests.Cases.References // We library should be gone. The `using` statement leaves no traces in the IL so nothing in `library` will be marked [RemovedAssembly ("library.dll")] -#if NETCOREAPP - [KeptReferencesInAssembly ("copied.dll", new[] { "System.Private.CoreLib" })] -#else - [KeptReferencesInAssembly ("copied.dll", new[] { "mscorlib" })] -#endif + [KeptReferencesInAssembly ("copied.dll", new[] { PlatformAssemblies.CoreLib, "library" })] public class AssemblyOnlyUsedByUsingWithCsc { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/References/AssemblyOnlyUsedByUsingWithCscWithKeepFacades.cs b/test/Mono.Linker.Tests.Cases/References/AssemblyOnlyUsedByUsingWithCscWithKeepFacades.cs index f039dac8fc46..2fcbb6a6c588 100644 --- a/test/Mono.Linker.Tests.Cases/References/AssemblyOnlyUsedByUsingWithCscWithKeepFacades.cs +++ b/test/Mono.Linker.Tests.Cases/References/AssemblyOnlyUsedByUsingWithCscWithKeepFacades.cs @@ -1,4 +1,5 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Helpers; using Mono.Linker.Tests.Cases.Expectations.Metadata; using Mono.Linker.Tests.Cases.References.Dependencies; @@ -22,11 +23,7 @@ namespace Mono.Linker.Tests.Cases.References // We library should be gone. The `using` statement leaves no traces in the IL so nothing in `library` will be marked [RemovedAssembly ("library.dll")] -#if NETCOREAPP - [KeptReferencesInAssembly ("copied.dll", new[] { "System.Private.CoreLib" })] -#else - [KeptReferencesInAssembly ("copied.dll", new[] { "mscorlib" })] -#endif + [KeptReferencesInAssembly ("copied.dll", new[] { PlatformAssemblies.CoreLib, "library" })] public class AssemblyOnlyUsedByUsingWithCscWithKeepFacades { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/Symbols/ReferenceWithEmbeddedPdbCopyAction.cs b/test/Mono.Linker.Tests.Cases/Symbols/ReferenceWithEmbeddedPdbCopyAction.cs index 15ee534aad51..3be34790d05e 100644 --- a/test/Mono.Linker.Tests.Cases/Symbols/ReferenceWithEmbeddedPdbCopyAction.cs +++ b/test/Mono.Linker.Tests.Cases/Symbols/ReferenceWithEmbeddedPdbCopyAction.cs @@ -8,7 +8,8 @@ namespace Mono.Linker.Tests.Cases.Symbols [SetupLinkerLinkSymbols ("false")] [SetupLinkerAction ("copy", "LibraryWithEmbeddedPdbSymbols")] - [RemovedSymbols ("LibraryWithEmbeddedPdbSymbols.dll")] + // Copy assemblies cannot be modified. + [KeptSymbols ("LibraryWithEmbeddedPdbSymbols.dll")] // Copying with symbol linking off is a little more complex for embedded pdbs. // Do a little extra asserting here to make sure the assembly wasn't accidentally linked diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/AttributeArgumentForwarded.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/AttributeArgumentForwarded.cs index 4c119d473aaa..6e740e71714a 100644 --- a/test/Mono.Linker.Tests.Cases/TypeForwarding/AttributeArgumentForwarded.cs +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/AttributeArgumentForwarded.cs @@ -5,10 +5,6 @@ namespace Mono.Linker.Tests.Cases.TypeForwarding { - // On .NET FW the built in compiler will drop the reference to the forwarder assembly when compiling the test assembly. - // Use roslyn to give consistent behavior across platforms - [SetupCSharpCompilerToUse ("csc")] - // Actions: // link - This assembly, Forwarder.dll and Implementation.dll [SetupLinkerDefaultAction ("link")] diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/AttributeArgumentForwardedWithCopyAction.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/AttributeArgumentForwardedWithCopyAction.cs index d67b53fab310..0c5d01131646 100644 --- a/test/Mono.Linker.Tests.Cases/TypeForwarding/AttributeArgumentForwardedWithCopyAction.cs +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/AttributeArgumentForwardedWithCopyAction.cs @@ -23,7 +23,7 @@ namespace Mono.Linker.Tests.Cases.TypeForwarding [SetupCompileAfter ("Implementation.dll", new[] { "Dependencies/ImplementationLibrary.cs" })] [SetupCompileAfter ("Forwarder.dll", new[] { "Dependencies/ForwarderLibrary.cs" }, references: new[] { "Implementation.dll" })] - [RemovedAssembly ("Forwarder.dll")] + [KeptMemberInAssembly ("Forwarder.dll", typeof (ImplementationLibrary))] [KeptMemberInAssembly ("Implementation.dll", typeof (ImplementationLibrary))] static class AttributeArgumentForwardedWithCopyAction { diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/AttributeEnumArgumentForwarded.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/AttributeEnumArgumentForwarded.cs new file mode 100644 index 000000000000..ad6d2d1e71c9 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/AttributeEnumArgumentForwarded.cs @@ -0,0 +1,32 @@ +using System; +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, Forwarder.dll and Implementation.dll + [SetupLinkerDefaultAction ("link")] + + [SetupCompileBefore ("Forwarder.dll", new[] { "Dependencies/MyEnum.cs" }, defines: new[] { "INCLUDE_REFERENCE_IMPL" })] + [SetupCompileBefore ("Attribute.dll", new[] { "Dependencies/AttributeWithEnumArgument.cs" }, references: new[] { "Forwarder.dll" })] + + // After compiling the test case we then replace the reference impl with implementation + type forwarder + [SetupCompileAfter ("Implementation.dll", new[] { "Dependencies/MyEnum.cs" })] + [SetupCompileAfter ("Forwarder.dll", new[] { "Dependencies/MyEnumForwarder.cs" }, references: new[] { "Implementation.dll" })] + + [KeptTypeInAssembly ("Forwarder.dll", typeof (UsedToReferenceForwarderAssembly))] + [KeptTypeInAssembly ("Implementation.dll", "Mono.Linker.Tests.Cases.TypeForwarding.Dependencies.MyEnum")] + class AttributeEnumArgumentForwarded + { + static void Main () + { + // For the issue to repro, the forwarder assembly must be processed by SweepStep before + // the attribute. Referencing it first in the test does this, even though it's not really + // a guarantee, since the assembly action dictionary doesn't guarantee order. + var _ = typeof (UsedToReferenceForwarderAssembly); + var _2 = typeof (AttributedType); + } + } +} diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/AttributesScopeUpdated.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/AttributesScopeUpdated.cs index 56e06ac6f848..1e01403be881 100644 --- a/test/Mono.Linker.Tests.Cases/TypeForwarding/AttributesScopeUpdated.cs +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/AttributesScopeUpdated.cs @@ -19,7 +19,7 @@ namespace Mono.Linker.Tests.Cases.TypeForwarding [SetupCompileAfter ("Implementation.dll", new[] { "Dependencies/ImplementationLibrary.cs" })] [SetupCompileAfter ("Forwarder.dll", new[] { "Dependencies/ForwarderLibrary.cs" }, references: new[] { "Implementation.dll" })] - [RemovedAssembly ("Forwarder.dll")] + [KeptMemberInAssembly ("Forwarder.dll", typeof (ImplementationLibrary))] [KeptMemberInAssembly ("Implementation.dll", typeof (ImplementationLibrary))] static class AttributesScopeUpdated { diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/AttributeWithEnumArgument.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/AttributeWithEnumArgument.cs new file mode 100644 index 000000000000..3b466b30ee0d --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/AttributeWithEnumArgument.cs @@ -0,0 +1,16 @@ +using System; + +namespace Mono.Linker.Tests.Cases.TypeForwarding.Dependencies +{ + public class AttributeWithEnumArgumentAttribute : Attribute + { + public AttributeWithEnumArgumentAttribute (MyEnum arg) + { + } + } + + [AttributeWithEnumArgument (MyEnum.A)] + public class AttributedType + { + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/ForwarderLibrary.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/ForwarderLibrary.cs index 441a971eb3f8..57aee6ea8daa 100644 --- a/test/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/ForwarderLibrary.cs +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/ForwarderLibrary.cs @@ -2,3 +2,6 @@ [assembly: System.Runtime.CompilerServices.TypeForwardedTo (typeof (Mono.Linker.Tests.Cases.TypeForwarding.Dependencies.ImplementationLibrary))] [assembly: System.Runtime.CompilerServices.TypeForwardedTo (typeof (Mono.Linker.Tests.Cases.TypeForwarding.Dependencies.ImplementationStruct))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(Mono.Linker.Tests.Cases.TypeForwarding.Dependencies.ImplementationLibraryAttribute))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(Mono.Linker.Tests.Cases.TypeForwarding.Dependencies.ImplementationLibraryImp))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(Mono.Linker.Tests.Cases.TypeForwarding.Dependencies.ImplementationLibraryInterface))] diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/ImplementationLibrary.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/ImplementationLibrary.cs index 86a37fdfcff1..211e2d14ffe9 100644 --- a/test/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/ImplementationLibrary.cs +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/ImplementationLibrary.cs @@ -6,14 +6,38 @@ namespace Mono.Linker.Tests.Cases.TypeForwarding.Dependencies { + public interface ImplementationLibraryInterface + { + public int GetDefaultImplementation () + { + return 42; + } + } + + public class ImplementationLibraryImp : ImplementationLibraryInterface + { + } + public class ImplementationLibrary { + public class ImplementationLibraryNestedType + { + public static int PropertyOnNestedType { get; set; } + } + + public static int someField = 42; + public string GetSomeValue () { return "Hello"; } } + [AttributeUsage (AttributeTargets.All)] + public class ImplementationLibraryAttribute : Attribute + { + } + public struct ImplementationStruct { public int Field; diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/MyEnum.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/MyEnum.cs new file mode 100644 index 000000000000..fe948858e672 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/MyEnum.cs @@ -0,0 +1,13 @@ +namespace Mono.Linker.Tests.Cases.TypeForwarding.Dependencies +{ + public enum MyEnum + { + A, + B, + C + } + + public class UsedToReferenceForwarderAssembly + { + } +} diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/MyEnumForwarder.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/MyEnumForwarder.cs new file mode 100644 index 000000000000..03d4cf48420b --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/MyEnumForwarder.cs @@ -0,0 +1,11 @@ +using System.Runtime.CompilerServices; +using Mono.Linker.Tests.Cases.TypeForwarding.Dependencies; + +[assembly: TypeForwardedTo (typeof (MyEnum))] + +namespace Mono.Linker.Tests.Cases.TypeForwarding.Dependencies +{ + public class UsedToReferenceForwarderAssembly + { + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/ReferenceImplementationLibrary.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/ReferenceImplementationLibrary.cs index 8796effb1385..4ec4203724d1 100644 --- a/test/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/ReferenceImplementationLibrary.cs +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/ReferenceImplementationLibrary.cs @@ -9,13 +9,37 @@ public class ReferenceImplementationLibrary } #if INCLUDE_REFERENCE_IMPL + public interface ImplementationLibraryInterface + { + public int GetDefaultImplementation () + { + return 42; + } + } + + public class ImplementationLibraryImp : ImplementationLibraryInterface + { + } + public class ImplementationLibrary { + public class ImplementationLibraryNestedType + { + public static int PropertyOnNestedType { get; set; } + } + + public static int someField = 0; + public string GetSomeValue () { return null; } } + [AttributeUsage (AttributeTargets.All)] + public class ImplementationLibraryAttribute : Attribute + { + } + public struct ImplementationStruct { public int Field; diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/SecurityAttributeScope.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/SecurityAttributeScope.cs index 17744e7bf991..47bc346a29d0 100644 --- a/test/Mono.Linker.Tests.Cases/TypeForwarding/SecurityAttributeScope.cs +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/SecurityAttributeScope.cs @@ -16,13 +16,14 @@ namespace Mono.Linker.Tests.Cases.TypeForwarding [SetupLinkerArgument ("--strip-security", "false")] [Define ("IL_ASSEMBLY_AVAILABLE")] [KeepTypeForwarderOnlyAssemblies ("false")] - [SetupLinkerAction ("copy", "Library.dll")] + [SetupLinkerAction ("copy", "Library")] [SetupCompileBefore ("Forwarder.dll", new[] { "Dependencies/SecurityAttributeForwarderLibrary.cs" })] [SetupCompileBefore ("Library.dll", new[] { "Dependencies/LibraryWithSecurityAttributes.il" }, new[] { "Forwarder.dll" })] // Sanity checks to verify the test was setup correctly [KeptTypeInAssembly ("Library.dll", "LibraryWithSecurityAttributes")] - [RemovedAssembly ("Forwarder.dll")] + // There's a reference to `Forwarder` in the copy assembly `Library`. + [KeptAssembly ("Forwarder.dll")] public class SecurityAttributeScope { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/UnusedForwarderWithAssemblyCopyIsKept.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/UnusedForwarderWithAssemblyCopyIsKept.cs new file mode 100644 index 000000000000..a6a78eeab44c --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/UnusedForwarderWithAssemblyCopyIsKept.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")] + + [KeptMemberInAssembly ("Forwarder.dll", typeof (ImplementationLibrary))] + [RemovedAssembly ("Implementation.dll")] + + public class UnusedForwarderWithAssemblyCopyIsKept + { + static void Main () + { + } + } +} diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedAndUnusedForwarderWithAssemblyCopy.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedAndUnusedForwarderWithAssemblyCopy.cs index 63f9e70c5121..fdd75a7f8664 100644 --- a/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedAndUnusedForwarderWithAssemblyCopy.cs +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedAndUnusedForwarderWithAssemblyCopy.cs @@ -19,12 +19,14 @@ namespace Mono.Linker.Tests.Cases.TypeForwarding [SetupCompileAfter ("Unused.dll", new[] { "Dependencies/AnotherLibrary.cs" })] [SetupCompileAfter ("Forwarder.dll", new[] { "Dependencies/ForwarderLibrary_2.cs" }, references: new[] { "Implementation.dll", "Unused.dll" })] - [KeptAssembly ("Forwarder.dll")] [KeptMemberInAssembly ("Implementation.dll", typeof (ImplementationLibrary), "GetSomeValue()")] + // The whole assembly is kept as is, since it is marked with the `copy` action. [KeptMemberInAssembly ("Forwarder.dll", typeof (ImplementationLibrary))] + [KeptMemberInAssembly ("Forwarder.dll", "Mono.Linker.Tests.Cases.TypeForwarding.Dependencies.AnotherLibrary`1")] + [KeptReferencesInAssembly ("Forwarder.dll", new[] { "System.Private.CoreLib", "Implementation", "Unused" })] + // Even though `Forwarder` references this assembly, none of its members are marked (none is used) and, since `Unused` + // has `link` action, it is removed. [RemovedAssembly ("Unused.dll")] - [RemovedForwarder ("Forwarder.dll", "Mono.Linker.Tests.Cases.TypeForwarding.Dependencies.AnotherLibrary`1")] - [RemovedAssemblyReference ("Forwarder.dll", "Unused")] class UsedAndUnusedForwarderWithAssemblyCopy { static void Main () diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByPreserveDependency.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByPreserveDependency.cs new file mode 100644 index 000000000000..4abfabc668e3 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByPreserveDependency.cs @@ -0,0 +1,33 @@ +using System.Runtime.CompilerServices; +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: + // copy - This assembly + // link - Forwarder.dll and Implementation.dll + [SetupLinkerAction ("copy", "test")] + [SetupCompileBefore ("FakeSystemAssembly.dll", new[] { "../PreserveDependencies/Dependencies/PreserveDependencyAttribute.cs" })] + [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" })] + + [KeptMemberInAssembly ("Forwarder.dll", typeof (ImplementationLibrary))] + [KeptMemberInAssembly ("Implementation.dll", typeof (ImplementationLibrary))] + + [Kept] + [KeptMember (".ctor()")] + public class UsedForwarderInCopyAssemblyKeptByPreserveDependency + { + [Kept] + [KeptAttributeAttribute (typeof (PreserveDependencyAttribute))] + [PreserveDependency ("*", "Mono.Linker.Tests.Cases.TypeForwarding.Dependencies.ImplementationLibrary", "Forwarder")] + public static void Main () + { + } + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByUsedCustomAttribute.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByUsedCustomAttribute.cs new file mode 100644 index 000000000000..5c1230cd6533 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByUsedCustomAttribute.cs @@ -0,0 +1,32 @@ +using System.Runtime.CompilerServices; +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: + // copy - This assembly + // link - Forwarder.dll and Implementation.dll + [SetupLinkerAction ("copy", "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" })] + + [KeptMemberInAssembly ("Forwarder.dll", typeof (ImplementationLibraryAttribute))] + [KeptMemberInAssembly ("Implementation.dll", typeof (ImplementationLibraryAttribute))] + + [Kept] + [KeptMember (".ctor()")] + public class UsedForwarderInCopyAssemblyKeptByUsedCustomAttribute + { + [Kept] + [KeptAttributeAttribute (typeof (ImplementationLibraryAttribute))] + [ImplementationLibrary] + public static void Main () + { + } + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByUsedField.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByUsedField.cs new file mode 100644 index 000000000000..961cf64f8586 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByUsedField.cs @@ -0,0 +1,31 @@ +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: + // copy - This assembly + // link - Forwarder.dll and Implementation.dll + [SetupLinkerAction ("copy", "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" })] + + [KeptAssembly ("Forwarder.dll")] + [KeptMemberInAssembly ("Implementation.dll", typeof (ImplementationLibrary))] + [KeptMemberInAssembly ("Implementation.dll", typeof (ImplementationLibrary), "someField")] + [KeptMemberInAssembly ("Forwarder.dll", typeof (ImplementationLibrary))] + + [Kept] + [KeptMember (".ctor()")] + public class UsedForwarderInCopyAssemblyKeptByUsedField + { + public static void Main () + { + int field = ImplementationLibrary.someField; + } + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByUsedInterface.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByUsedInterface.cs new file mode 100644 index 000000000000..8b87e16a2df0 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByUsedInterface.cs @@ -0,0 +1,32 @@ +using System.Runtime.CompilerServices; +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: + // copy - This assembly + // link - Forwarder.dll and Implementation.dll + [SetupLinkerAction ("copy", "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" })] + + [KeptMemberInAssembly ("Forwarder.dll", typeof (ImplementationLibraryImp))] + [KeptMemberInAssembly ("Forwarder.dll", typeof (ImplementationLibraryInterface))] + [KeptMemberInAssembly ("Implementation.dll", typeof (ImplementationLibraryImp))] + [KeptMemberInAssembly ("Implementation.dll", typeof (ImplementationLibraryInterface))] + + [Kept] + [KeptMember (".ctor()")] + public class UsedForwarderInCopyAssemblyKeptByUsedInterface + { + public static void Main () + { + ImplementationLibraryInterface myInterface = new ImplementationLibraryImp (); + } + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByUsedMethod.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByUsedMethod.cs new file mode 100644 index 000000000000..7f574762a836 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByUsedMethod.cs @@ -0,0 +1,31 @@ +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: + // copy - This assembly + // link - Forwarder.dll and Implementation.dll + [SetupLinkerAction ("copy", "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" })] + + [KeptAssembly ("Forwarder.dll")] + [KeptMemberInAssembly ("Implementation.dll", typeof (ImplementationLibrary))] + [KeptMemberInAssembly ("Implementation.dll", typeof (ImplementationLibrary), "GetSomeValue()")] + [KeptMemberInAssembly ("Forwarder.dll", typeof (ImplementationLibrary))] + + [Kept] + [KeptMember (".ctor()")] + public class UsedForwarderInCopyAssemblyKeptByUsedMethod + { + public static void Main () + { + new ImplementationLibrary ().GetSomeValue (); + } + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByUsedNestedType.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByUsedNestedType.cs new file mode 100644 index 000000000000..56e952b2551e --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByUsedNestedType.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +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: + // copy - This assembly + // link - Forwarder.dll and Implementation.dll + [SetupLinkerAction ("copy", "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" })] + + [KeptAssembly ("Forwarder.dll")] + [KeptMemberInAssembly ("Implementation.dll", typeof (ImplementationLibrary.ImplementationLibraryNestedType))] + [KeptMemberInAssembly ("Forwarder.dll", typeof (ImplementationLibrary.ImplementationLibraryNestedType))] + + [Kept] + [KeptMember (".ctor()")] + public class UsedForwarderInCopyAssemblyKeptByUsedNestedType + { + public static void Main () + { + new ImplementationLibrary.ImplementationLibraryNestedType (); + } + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByUsedProperty.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByUsedProperty.cs new file mode 100644 index 000000000000..e92f5e716aa7 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByUsedProperty.cs @@ -0,0 +1,32 @@ +using System.Runtime.CompilerServices; +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: + // copy - This assembly + // link - Forwarder.dll and Implementation.dll + [SetupLinkerAction ("copy", "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" })] + + [KeptMemberInAssembly ("Forwarder.dll", typeof (ImplementationLibrary))] + [KeptMemberInAssembly ("Forwarder.dll", typeof (ImplementationLibrary.ImplementationLibraryNestedType))] + [KeptMemberInAssembly ("Implementation.dll", typeof (ImplementationLibrary))] + [KeptMemberInAssembly ("Implementation.dll", typeof (ImplementationLibrary.ImplementationLibraryNestedType))] + + [Kept] + [KeptMember (".ctor()")] + public class UsedForwarderInCopyAssemblyKeptByUsedProperty + { + public static void Main () + { + var accessPropertyOnNestedType = ImplementationLibrary.ImplementationLibraryNestedType.PropertyOnNestedType; + } + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByUsedTypeAsGenericArg.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByUsedTypeAsGenericArg.cs new file mode 100644 index 000000000000..1693a506e022 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderInCopyAssemblyKeptByUsedTypeAsGenericArg.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +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: + // copy - This assembly + // link - Forwarder.dll and Implementation.dll + [SetupLinkerAction ("copy", "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" })] + + [KeptAssembly ("Forwarder.dll")] + [KeptMemberInAssembly ("Implementation.dll", typeof (ImplementationLibrary))] + [KeptMemberInAssembly ("Forwarder.dll", typeof (ImplementationLibrary))] + + [Kept] + [KeptMember (".ctor()")] + public class UsedForwarderInCopyAssemblyKeptByUsedTypeAsGenericArg + { + public static void Main () + { + _ = new List (); + } + } +} \ 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..963d22da9485 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderIsDynamicallyAccessedWithAssemblyCopyUsed.cs @@ -0,0 +1,38 @@ +// 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 ("copyused", "Forwarder")] + [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" })] + + [KeptMemberInAssembly ("Forwarder.dll", typeof (ImplementationLibrary))] + [KeptMemberInAssembly ("Implementation.dll", typeof (ImplementationLibrary))] + 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/UsedForwarderWithAssemblyCopyUsedAndFacadesKept.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderWithAssemblyCopyUsedAndFacadesKept.cs index 9c7b0c76962b..7698bc70c10e 100644 --- a/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderWithAssemblyCopyUsedAndFacadesKept.cs +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedForwarderWithAssemblyCopyUsedAndFacadesKept.cs @@ -20,6 +20,7 @@ namespace Mono.Linker.Tests.Cases.TypeForwarding [KeptMemberInAssembly ("Forwarder.dll", typeof (ImplementationLibrary))] [KeptMemberInAssembly ("Implementation.dll", typeof (ImplementationLibrary), "GetSomeValue()")] + [RemovedAssemblyReference ("test", "Forwarder")] class UsedForwarderWithAssemblyCopyUsedAndFacadesKept { static void Main () diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderInCopyAssemblyIsDynamicallyAccessed.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderInCopyAssemblyIsDynamicallyAccessed.cs new file mode 100644 index 000000000000..4ba936ae614c --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderInCopyAssemblyIsDynamicallyAccessed.cs @@ -0,0 +1,41 @@ +// 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 +{ + [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" })] + [SetupLinkerAction ("copy", "FirstForwarder")] + + [KeptMemberInAssembly ("FirstForwarder.dll", typeof (ImplementationLibrary))] + // Dynamically accessing a type forwarder will cause the linker to mark the scope + // of type pointed to as well as the resolved type. + [KeptMemberInAssembly ("SecondForwarder.dll", typeof (ImplementationLibrary))] + [KeptMemberInAssembly ("Implementation.dll", typeof (ImplementationLibrary), "GetSomeValue()")] + class UsedTransitiveForwarderInCopyAssemblyIsDynamicallyAccessed + { + static void Main () + { + // [copy] [link] [link] + // FirstForwarder -> SecondForwarder -> Implementation + PointToTypeInFacade ("Mono.Linker.Tests.Cases.TypeForwarding.Dependencies.ImplementationLibrary, FirstForwarder"); + } + + [Kept] + static void PointToTypeInFacade ( + [KeptAttributeAttribute (typeof(DynamicallyAccessedMembersAttribute))] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] string typeName) + { + } + } +} diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderInCopyUsedAssemblyIsDynamicallyAccessed.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderInCopyUsedAssemblyIsDynamicallyAccessed.cs new file mode 100644 index 000000000000..9f6b74c0d794 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderInCopyUsedAssemblyIsDynamicallyAccessed.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 +{ + [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" })] + [SetupLinkerAction ("copyused", "FirstForwarder")] + + [KeptMemberInAssembly ("FirstForwarder.dll", typeof (ImplementationLibrary))] + [KeptMemberInAssembly ("Implementation.dll", typeof (ImplementationLibrary), "GetSomeValue()")] + [RemovedAssemblyReference ("FirstForwarder.dll", "SecondForwarder.dll")] + [RemovedForwarder ("FirstForwarder.dll", nameof (ImplementationStruct))] + [RemovedAssembly ("SecondForwarder.dll")] + class UsedTransitiveForwarderInCopyUsedAssemblyIsDynamicallyAccessed + { + static void Main () + { + // [copyused] [link] [link] + // FirstForwarder -> SecondForwarder -> Implementation + PointToTypeInFacade ("Mono.Linker.Tests.Cases.TypeForwarding.Dependencies.ImplementationLibrary, FirstForwarder"); + } + + [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..d51014d0ed37 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderIsDynamicallyAccessed.cs @@ -0,0 +1,41 @@ +// 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 +{ + [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" })] + + [KeptMemberInAssembly ("FirstForwarder.dll", typeof (ImplementationLibrary), "GetSomeValue()")] + [KeptMemberInAssembly ("Implementation.dll", typeof (ImplementationLibrary), "GetSomeValue()")] + [RemovedForwarder ("FirstForwarder.dll", nameof (ImplementationStruct))] + [RemovedAssembly ("SecondForwarder.dll")] + class UsedTransitiveForwarderIsDynamicallyAccessed + { + static void Main () + { + // [link] [link] [link] + // FirstForwarder -> SecondForwarder -> Implementation + PointToTypeInFacade ("Mono.Linker.Tests.Cases.TypeForwarding.Dependencies.ImplementationLibrary, FirstForwarder"); + } + + [Kept] + static void PointToTypeInFacade ( + [KeptAttributeAttribute (typeof(DynamicallyAccessedMembersAttribute))] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] string typeName) + { + } + } +} diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderIsResolvedAndFacadeRemoved.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderIsResolvedAndFacadeRemoved.cs new file mode 100644 index 000000000000..1c8458ebaf9c --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderIsResolvedAndFacadeRemoved.cs @@ -0,0 +1,31 @@ +// 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 +{ + [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" })] + [SetupCompileAfter ("Implementation.dll", new[] { "Dependencies/ImplementationLibrary.cs" })] + [SetupCompileAfter ("SecondForwarder.dll", new[] { "Dependencies/ForwarderLibrary.cs" }, references: new[] { "Implementation.dll" })] + + [KeptMemberInAssembly ("Implementation.dll", typeof (ImplementationLibrary), "GetSomeValue()")] + [RemovedMemberInAssembly ("Implementation.dll", nameof (ImplementationStruct))] + [RemovedAssembly ("FirstForwarder.dll")] + [RemovedAssembly ("SecondForwarder.dll")] + class UsedTransitiveForwarderIsResolvedAndFacadeRemoved + { + static void Main () + { + var instance = new ImplementationLibrary (); + instance.GetSomeValue (); + } + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderIsResolvedAndFacadeRemovedInCopyAssembly.cs b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderIsResolvedAndFacadeRemovedInCopyAssembly.cs new file mode 100644 index 000000000000..3472be667d81 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/TypeForwarding/UsedTransitiveForwarderIsResolvedAndFacadeRemovedInCopyAssembly.cs @@ -0,0 +1,30 @@ +// 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 +{ + [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" })] + [SetupLinkerAction ("copy", "Implementation")] + + [KeptMemberInAssembly ("Implementation.dll", typeof (ImplementationLibrary), "GetSomeValue()")] + [RemovedMemberInAssembly ("Implementation.dll", nameof (ImplementationStruct))] + class UsedTransitiveForwarderIsResolvedAndFacadeRemovedInCopyAssembly + { + static void Main () + { + var instance = new ImplementationLibrary (); + instance.GetSomeValue (); + } + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests/TestCasesRunner/ExpectationsProvider.cs b/test/Mono.Linker.Tests/TestCasesRunner/ExpectationsProvider.cs index 0e87979f46c3..41bff0e3b6c6 100644 --- a/test/Mono.Linker.Tests/TestCasesRunner/ExpectationsProvider.cs +++ b/test/Mono.Linker.Tests/TestCasesRunner/ExpectationsProvider.cs @@ -1,5 +1,6 @@ using Mono.Cecil; using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; namespace Mono.Linker.Tests.TestCasesRunner { @@ -8,7 +9,10 @@ public static class ExpectationsProvider public static bool IsAssemblyAssertion (CustomAttribute attr) { - return attr.AttributeType.Name == nameof (KeptAssemblyAttribute) || attr.AttributeType.Name == nameof (RemovedAssemblyAttribute); + return attr.AttributeType.Name == nameof (KeptAssemblyAttribute) || + attr.AttributeType.Name == nameof (RemovedAssemblyAttribute) || + attr.AttributeType.Name == nameof (SetupLinkerActionAttribute) || + attr.AttributeType.Name == nameof (SetupLinkerTrimModeAttribute); } public static bool IsSymbolAssertion (CustomAttribute attr) diff --git a/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs b/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs index f0e2fd4cb0f5..66ee602a56b5 100644 --- a/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs +++ b/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs @@ -6,6 +6,7 @@ using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; using Mono.Linker.Tests.Extensions; using NUnit.Framework; @@ -108,6 +109,8 @@ protected AssemblyDefinition ResolveOriginalsAssembly (string assemblyName) void PerformOutputAssemblyChecks (AssemblyDefinition original, NPath outputDirectory) { var assembliesToCheck = original.MainModule.Types.SelectMany (t => t.CustomAttributes).Where (attr => ExpectationsProvider.IsAssemblyAssertion (attr)); + var actionAssemblies = new HashSet (); + bool trimModeIsCopy = false; foreach (var assemblyAttr in assembliesToCheck) { var name = (string) assemblyAttr.ConstructorArguments.First ().Value; @@ -117,9 +120,29 @@ void PerformOutputAssemblyChecks (AssemblyDefinition original, NPath outputDirec Assert.IsFalse (expectedPath.FileExists (), $"Expected the assembly {name} to not exist in {outputDirectory}, but it did"); else if (assemblyAttr.AttributeType.Name == nameof (KeptAssemblyAttribute)) Assert.IsTrue (expectedPath.FileExists (), $"Expected the assembly {name} to exist in {outputDirectory}, but it did not"); - else + else if (assemblyAttr.AttributeType.Name == nameof (SetupLinkerActionAttribute)) { + string assemblyName = (string) assemblyAttr.ConstructorArguments[1].Value; + if ((string) assemblyAttr.ConstructorArguments[0].Value == "copy") { + VerifyCopyAssemblyIsKeptUnmodified (outputDirectory, assemblyName + (assemblyName == "test" ? ".exe" : ".dll")); + } + + actionAssemblies.Add (assemblyName); + } else if (assemblyAttr.AttributeType.Name == nameof (SetupLinkerTrimModeAttribute)) { + // We delay checking that everything was copied after processing all assemblies + // with a specific action, since assembly action wins over trim mode. + if ((string) assemblyAttr.ConstructorArguments[0].Value == "copy") + trimModeIsCopy = true; + } else throw new NotImplementedException ($"Unknown assembly assertion of type {assemblyAttr.AttributeType}"); } + + if (trimModeIsCopy) { + foreach (string assemblyName in Directory.GetFiles (Directory.GetParent (outputDirectory).ToString (), "input")) { + var fileInfo = new FileInfo (assemblyName); + if (fileInfo.Extension == ".dll" && !actionAssemblies.Contains (assemblyName)) + VerifyCopyAssemblyIsKeptUnmodified (outputDirectory, assemblyName + (assemblyName == "test" ? ".exe" : ".dll")); + } + } } void PerformOutputSymbolChecks (AssemblyDefinition original, NPath outputDirectory) @@ -212,12 +235,19 @@ void VerifyLinkingOfOtherAssemblies (AssemblyDefinition original) } var expectedTypeName = checkAttrInAssembly.ConstructorArguments[1].Value.ToString (); - var linkedType = linkedAssembly.MainModule.GetType (expectedTypeName); + TypeDefinition linkedType = linkedAssembly.MainModule.GetType (expectedTypeName); if (linkedType == null && linkedAssembly.MainModule.HasExportedTypes) { - linkedType = linkedAssembly.MainModule.ExportedTypes - .FirstOrDefault (exported => exported.FullName == expectedTypeName) - ?.Resolve (); + ExportedType exportedType = linkedAssembly.MainModule.ExportedTypes + .FirstOrDefault (exported => exported.FullName == expectedTypeName); + + // Note that copied assemblies could have dangling references. + if (exportedType != null && original.EntryPoint.DeclaringType.CustomAttributes.FirstOrDefault ( + ca => ca.AttributeType.Name == nameof (RemovedAssemblyAttribute) + && ca.ConstructorArguments[0].Value.ToString () == exportedType.Scope.Name + ".dll") != null) + continue; + + linkedType = exportedType?.Resolve (); } switch (attributeTypeName) { @@ -374,6 +404,17 @@ void VerifyAttributeInAssembly (CustomAttribute inAssemblyAttribute, AssemblyDef Assert.Fail ($"Invalid test assertion. No member named `{memberName}` exists on the original type `{originalType}`"); } + void VerifyCopyAssemblyIsKeptUnmodified (NPath outputDirectory, string assemblyName) + { + string inputAssemblyPath = Path.Combine (Directory.GetParent (outputDirectory).ToString (), "input", assemblyName); + string outputAssemblyPath = Path.Combine (outputDirectory, assemblyName); + Assert.IsTrue (File.ReadAllBytes (inputAssemblyPath).SequenceEqual (File.ReadAllBytes (outputAssemblyPath)), + $"Expected assemblies\n" + + $"\t{inputAssemblyPath}\n" + + $"\t{outputAssemblyPath}\n" + + $"binaries to be equal, since the input assembly has copy action."); + } + void VerifyCustomAttributeKept (ICustomAttributeProvider provider, string expectedAttributeTypeName) { var match = provider.CustomAttributes.FirstOrDefault (attr => attr.AttributeType.FullName == expectedAttributeTypeName); @@ -567,7 +608,11 @@ protected virtual bool TryVerifyKeptMemberInAssemblyAsMethod (string memberName, void VerifyKeptReferencesInAssembly (CustomAttribute inAssemblyAttribute) { var assembly = ResolveLinkedAssembly (inAssemblyAttribute.ConstructorArguments[0].Value.ToString ()); - var expectedReferenceNames = ((CustomAttributeArgument[]) inAssemblyAttribute.ConstructorArguments[1].Value).Select (attr => (string) attr.Value); + var expectedReferenceNames = ((CustomAttributeArgument[]) inAssemblyAttribute.ConstructorArguments[1].Value).Select (attr => (string) attr.Value).ToList (); + for (int i = 0; i < expectedReferenceNames.Count (); i++) + if (expectedReferenceNames[i].EndsWith (".dll")) + expectedReferenceNames[i] = expectedReferenceNames[i].Substring (0, expectedReferenceNames[i].LastIndexOf (".")); + Assert.That (assembly.MainModule.AssemblyReferences.Select (asm => asm.Name), Is.EquivalentTo (expectedReferenceNames)); }