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 ();
+ }
+ }
+ }
+}