diff --git a/src/linker/Linker.Dataflow/DynamicallyAccessedMembersTypeHierarchy.cs b/src/linker/Linker.Dataflow/DynamicallyAccessedMembersTypeHierarchy.cs index d9d8e30e16a2..4d310f192e3c 100644 --- a/src/linker/Linker.Dataflow/DynamicallyAccessedMembersTypeHierarchy.cs +++ b/src/linker/Linker.Dataflow/DynamicallyAccessedMembersTypeHierarchy.cs @@ -91,6 +91,14 @@ public DynamicallyAccessedMembersTypeHierarchy (LinkContext context, MarkStep ma Debug.Assert (!apply || annotation != DynamicallyAccessedMemberTypes.None); + // If OptimizeTypeHierarchyAnnotations is disabled, we will apply the annotations without seeing object.GetType() + bool applyOptimizeTypeHierarchyAnnotations = (annotation != DynamicallyAccessedMemberTypes.None) && !_context.IsOptimizationEnabled (CodeOptimizations.OptimizeTypeHierarchyAnnotations, type); + // Unfortunately, we cannot apply the annotation to type derived from EventSource - Revisit after https://github.com/dotnet/runtime/issues/54859 + // Breaking the logic to make it easier to maintain in the future since the logic is convoluted + // DisableEventSourceSpecialHandling is closely tied to a type derived from EventSource and should always go together + // However, logically it should be possible to use DisableEventSourceSpecialHandling to allow marking types derived from EventSource when OptimizeTypeHierarchyAnnotations is disabled + apply |= applyOptimizeTypeHierarchyAnnotations && (_context.DisableEventSourceSpecialHandling || !BCL.EventTracingForWindows.IsEventSourceImplementation (type, _context)); + // Store the results in the cache // Don't store empty annotations for non-interface types - we can use the presence of the row // in the cache as indication of it instead. diff --git a/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs b/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs index 28e0e0a98d2e..c8727a09b8b9 100644 --- a/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs +++ b/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs @@ -124,6 +124,9 @@ public void ApplyDynamicallyAccessedMembersToType (ref ReflectionPatternContext Debug.Assert (annotation != DynamicallyAccessedMemberTypes.None); reflectionPatternContext.AnalyzingPattern (); + // Handle cases where a type has no members but annotations are to be applied to derived type members + reflectionPatternContext.RecordHandledPattern (); + MarkTypeForDynamicallyAccessedMembers (ref reflectionPatternContext, type, annotation); } diff --git a/src/linker/Linker.Steps/MarkStep.cs b/src/linker/Linker.Steps/MarkStep.cs index f323faa08652..7bb060381d23 100644 --- a/src/linker/Linker.Steps/MarkStep.cs +++ b/src/linker/Linker.Steps/MarkStep.cs @@ -1776,7 +1776,8 @@ protected internal virtual TypeDefinition MarkType (TypeReference reference, Dep MarkSerializable (type); // This marks static fields of KeyWords/OpCodes/Tasks subclasses of an EventSource type. - if (BCL.EventTracingForWindows.IsEventSourceImplementation (type, _context)) { + // The special handling of EventSource is still needed in .NET6 in library mode + if ((!_context.DisableEventSourceSpecialHandling || _context.GetTargetRuntimeVersion () < TargetRuntimeVersion.NET6) && BCL.EventTracingForWindows.IsEventSourceImplementation (type, _context)) { MarkEventSourceProviders (type); } @@ -1911,7 +1912,8 @@ void MarkTypeSpecialCustomAttributes (TypeDefinition type) case "DebuggerTypeProxyAttribute" when attrType.Namespace == "System.Diagnostics": MarkTypeWithDebuggerTypeProxyAttribute (type, attribute); break; - case "EventDataAttribute" when attrType.Namespace == "System.Diagnostics.Tracing": + // The special handling of EventSource is still needed in .NET6 in library mode + case "EventDataAttribute" when attrType.Namespace == "System.Diagnostics.Tracing" && (!_context.DisableEventSourceSpecialHandling || _context.GetTargetRuntimeVersion () < TargetRuntimeVersion.NET6): if (MarkMethodsIf (type.Methods, MethodDefinitionExtensions.IsPublicInstancePropertyMethod, new DependencyInfo (DependencyKind.ReferencedBySpecialAttribute, type))) Tracer.AddDirectDependency (attribute, new DependencyInfo (DependencyKind.CustomAttribute, type), marked: false); break; @@ -2377,6 +2379,7 @@ protected virtual bool AlwaysMarkTypeAsInstantiated (TypeDefinition td) void MarkEventSourceProviders (TypeDefinition td) { + Debug.Assert (_context.GetTargetRuntimeVersion () < TargetRuntimeVersion.NET6 || !_context.DisableEventSourceSpecialHandling); foreach (var nestedType in td.NestedTypes) { if (BCL.EventTracingForWindows.IsProviderName (nestedType.Name)) MarkStaticFields (nestedType, new DependencyInfo (DependencyKind.EventSourceProviderField, td)); diff --git a/src/linker/Linker.Steps/RootAssemblyInputStep.cs b/src/linker/Linker.Steps/RootAssemblyInputStep.cs index e2aa901b720a..e8fedd4a9ad6 100644 --- a/src/linker/Linker.Steps/RootAssemblyInputStep.cs +++ b/src/linker/Linker.Steps/RootAssemblyInputStep.cs @@ -81,7 +81,11 @@ protected override void Process () CodeOptimizations.RemoveDescriptors | CodeOptimizations.RemoveLinkAttributes | CodeOptimizations.RemoveSubstitutions | - CodeOptimizations.RemoveDynamicDependencyAttribute, assembly.Name.Name); + CodeOptimizations.RemoveDynamicDependencyAttribute | + CodeOptimizations.OptimizeTypeHierarchyAnnotations, assembly.Name.Name); + + // Enable EventSource special handling + Context.DisableEventSourceSpecialHandling = false; // No metadata trimming Context.MetadataTrimming = MetadataTrimming.None; diff --git a/src/linker/Linker/LinkContext.cs b/src/linker/Linker/LinkContext.cs index 61a6b17e420b..9e6221fdc21c 100644 --- a/src/linker/Linker/LinkContext.cs +++ b/src/linker/Linker/LinkContext.cs @@ -125,6 +125,12 @@ public bool IgnoreUnresolved { public bool DisableOperatorDiscovery { get; set; } + /// + /// Option to not special case EventSource. + /// Currently, values are hard-coded and does not have a command line option to control + /// + public bool DisableEventSourceSpecialHandling { get; set; } + public bool IgnoreDescriptors { get; set; } public bool IgnoreSubstitutions { get; set; } @@ -246,7 +252,10 @@ public LinkContext (Pipeline pipeline, ILogger logger) CodeOptimizations.RemoveDescriptors | CodeOptimizations.RemoveLinkAttributes | CodeOptimizations.RemoveSubstitutions | - CodeOptimizations.RemoveDynamicDependencyAttribute; + CodeOptimizations.RemoveDynamicDependencyAttribute | + CodeOptimizations.OptimizeTypeHierarchyAnnotations; + + DisableEventSourceSpecialHandling = true; Optimizations = new CodeOptimizationsSettings (defaultOptimizations); } @@ -986,5 +995,12 @@ public enum CodeOptimizations RemoveSubstitutions = 1 << 21, RemoveLinkAttributes = 1 << 22, RemoveDynamicDependencyAttribute = 1 << 23, + + /// + /// Option to apply annotations to type heirarchy + /// Enable type heirarchy apply in library mode to annotate derived types eagerly + /// Otherwise, type annotation will only be applied with calls to object.GetType() + /// + OptimizeTypeHierarchyAnnotations = 1 << 24, } } diff --git a/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/CustomEventSource.cs b/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/CustomEventSource.cs index ebeb48c1cd92..06de7d17180f 100644 --- a/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/CustomEventSource.cs +++ b/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/CustomEventSource.cs @@ -8,7 +8,8 @@ public class CustomEventSource { public static void Main () { - var b = MyCompanyEventSource.Log.IsEnabled (); + // This call will trigger Object.GetType() Reflection pattern that will preserve all + EventSource.GenerateManifest (typeof (MyCompanyEventSource), null); } } @@ -21,29 +22,41 @@ public static void Main () [EventSource (Name = "MyCompany")] class MyCompanyEventSource : EventSource { + [KeptMember (".ctor()")] [Kept] public class Keywords { [Kept] public const EventKeywords Page = (EventKeywords) 1; + [Kept] public int Unused; } + [KeptMember (".ctor()")] [Kept] public class Tasks { [Kept] public const EventTask Page = (EventTask) 1; + [Kept] public int Unused; } + [KeptMember (".ctor()")] + [Kept] class NotMatching { } [Kept] public static MyCompanyEventSource Log = new MyCompanyEventSource (); + + [Kept] + int private_member; + + [Kept] + void PrivateMethod () { } } } diff --git a/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/CustomLibraryEventSource.cs b/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/CustomLibraryEventSource.cs new file mode 100644 index 000000000000..c4d0e2fabfed --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/CustomLibraryEventSource.cs @@ -0,0 +1,58 @@ +using System; +using System.Diagnostics.Tracing; +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.BCLFeatures.ETW +{ + [SetupLinkerArgument ("-a", "test.exe", "library")] + [KeptMember (".ctor()")] + public class CustomLibraryEventSource + { + public static void Main () + { + // Reference to a derived EventSource but does not trigger Object.GetType() + var b = CustomEventSourceInLibraryMode.Log.IsEnabled (); + } + } + + [Kept] + [KeptBaseType (typeof (EventSource))] + [KeptAttributeAttribute (typeof (EventSourceAttribute))] + [KeptMember (".ctor()")] + [KeptMember (".cctor()")] + + [EventSource (Name = "MyLibraryCompany")] + class CustomEventSourceInLibraryMode : EventSource + { + // In library mode, we special case nested types + [Kept] + public class Keywords + { + [Kept] + public const EventKeywords Page = (EventKeywords) 1; + + public int Unused; + } + + [Kept] + public class Tasks + { + [Kept] + public const EventTask Page = (EventTask) 1; + + public int Unused; + } + + class NotMatching + { + } + + [Kept] + public static CustomEventSourceInLibraryMode Log = new CustomEventSourceInLibraryMode (); + + int private_member; + + void PrivateMethod () { } + } +} diff --git a/test/Mono.Linker.Tests.Cases/Reflection/MembersUsedViaReflection.cs b/test/Mono.Linker.Tests.Cases/Reflection/MembersUsedViaReflection.cs index a9af87ddc2da..5a7fe375eb9c 100644 --- a/test/Mono.Linker.Tests.Cases/Reflection/MembersUsedViaReflection.cs +++ b/test/Mono.Linker.Tests.Cases/Reflection/MembersUsedViaReflection.cs @@ -194,7 +194,6 @@ private int PrivateProperty { [Kept] public static class PublicNestedType { - // PublicNestedType should be kept but linker won't mark anything besides the declaration [Kept] public static int _nestedPublicField; [Kept] @@ -261,7 +260,6 @@ private int PrivateProperty { [Kept] public static class PublicNestedType { - // PublicNestedType should be kept but linker won't mark anything besides the declaration [Kept] public static int _nestedPublicField; [Kept] @@ -318,7 +316,6 @@ private int PrivateProperty { [Kept] public static class PublicNestedType { - // PublicNestedType should be kept but linker won't mark anything besides the declaration [Kept] public static int _nestedPublicField; [Kept] @@ -375,7 +372,6 @@ private int PrivateProperty { [Kept] public static class PublicNestedType { - // PublicNestedType should be kept but linker won't mark anything besides the declaration [Kept] public static int _nestedPublicField; [Kept] @@ -432,7 +428,6 @@ private int PrivateProperty { [Kept] public static class PublicNestedType { - // PublicNestedType should be kept but linker won't mark anything besides the declaration [Kept] public static int _nestedPublicField; [Kept] diff --git a/test/Mono.Linker.Tests.Cases/Reflection/ObjectGetTypeLibraryMode.cs b/test/Mono.Linker.Tests.Cases/Reflection/ObjectGetTypeLibraryMode.cs new file mode 100644 index 000000000000..804de601b3b4 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/Reflection/ObjectGetTypeLibraryMode.cs @@ -0,0 +1,158 @@ +// 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; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text; +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Helpers; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.Reflection +{ + [SetupLinkerArgument ("-a", "test.exe", "library")] + [ExpectedNoWarnings] + [KeptMember (".ctor()")] + public class ObjectGetTypeLibraryMode + { + public static void Main () + { + BasicAnnotationWithNoDerivedClasses.Test (); + BasicNoAnnotationWithNoDerivedClasses.Test (); + } + + [Kept] + class BasicAnnotationWithNoDerivedClasses + { + [Kept] + [KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute))] + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] + public interface IBasicAnnotatedInterface + { + } + + [Kept] + [KeptMember (".ctor()")] + [KeptInterface (typeof (IBasicAnnotatedInterface))] + class ClassImplementingAnnotatedInterface : IBasicAnnotatedInterface + { + [Kept] + public void UsedMethod () { } + [Kept] // The type is not sealed, so trimmer will apply the annotation from the interface + public void UnusedMethod () { } + } + + [Kept] + static void TestInterface () + { + var classImplementingInterface = new ClassImplementingAnnotatedInterface (); + } + + [Kept] + [KeptMember (".ctor()")] + [KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute))] + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] + class BasicAnnotatedClass + { + [Kept] + public void UsedMethod () { } + [Kept] // The type is not sealed, so trimmer will apply the annotation from the interface + public void UnusedMethod () { } + } + + [Kept] + static void TestClass () + { + var instance = new BasicAnnotatedClass (); + } + + [Kept] + [KeptMember (".ctor()")] + [KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute))] + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] + struct BasicAnnotatedStruct + { + [Kept] + public void UsedMethod () { } + [Kept] + public void UnusedMethod () { } + } + + [Kept] + static void TestStruct () + { + var instance = new BasicAnnotatedStruct (); + } + + [Kept] + public static void Test () + { + TestInterface (); + TestClass (); + TestStruct (); + } + } + + [Kept] + class BasicNoAnnotationWithNoDerivedClasses + { + [Kept] + public interface IBasicNoAnnotatedInterface + { + } + + [Kept] + [KeptMember (".ctor()")] + [KeptInterface (typeof (IBasicNoAnnotatedInterface))] + class ClassImplementingNoAnnotatedInterface : IBasicNoAnnotatedInterface + { + public void UsedMethod () { } + public void UnusedMethod () { } + } + + [Kept] + static void TestInterface () + { + var classImplementingInterface = new ClassImplementingNoAnnotatedInterface (); + } + + [Kept] + [KeptMember (".ctor()")] + class BasicNoAnnotatedClass + { + public void UsedMethod () { } + public void UnusedMethod () { } + } + + [Kept] + static void TestClass () + { + var instance = new BasicNoAnnotatedClass (); + } + + [Kept] + [KeptMember (".ctor()")] + struct BasicNoAnnotatedStruct + { + public void UsedMethod () { } + public void UnusedMethod () { } + } + + [Kept] + static void TestStruct () + { + var instance = new BasicNoAnnotatedStruct (); + } + + [Kept] + public static void Test () + { + TestInterface (); + TestClass (); + TestStruct (); + } + } + } +}