From 0efdb16d93637b02cb11018bbc12e0a6ec9de6f6 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Thu, 5 Mar 2026 23:28:02 -0800 Subject: [PATCH] Remove some unnecessary suppressions Due to the DynamicallyAccessedMembersAttribute on the EventSource type, all members that have DAM/RUC on them produce warnings that need to be suppressed. In NAOT it also causes extra metadata to be preserved. By moving these members into a nested type, it excludes them from reflection visibility and means we don't have to have suppressions. --- .../System/Diagnostics/Tracing/EventSource.cs | 717 +++++++++--------- 1 file changed, 357 insertions(+), 360 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventSource.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventSource.cs index e998fad4fdd73f..484293f89d25aa 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventSource.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventSource.cs @@ -356,7 +356,7 @@ public static Guid GetGuid(Type eventSourceType) { ArgumentNullException.ThrowIfNull(eventSourceType); - EventSourceAttribute? attrib = (EventSourceAttribute?)GetCustomAttributeHelper(eventSourceType, typeof(EventSourceAttribute)); + EventSourceAttribute? attrib = (EventSourceAttribute?)EventSourceHelpers.GetCustomAttributeHelper(eventSourceType, typeof(EventSourceAttribute)); string name = eventSourceType.Name; if (attrib != null) { @@ -412,6 +412,7 @@ public static string GetName(Type eventSourceType) { return GenerateManifest(eventSourceType, assemblyPathToIncludeInManifest, EventManifestOptions.None); } + /// /// Returns a string of the XML manifest associated with the eventSourceType. The scheme for this XML is /// documented at in EventManifest Schema https://learn.microsoft.com/windows/desktop/WES/eventmanifestschema-schema. @@ -441,7 +442,7 @@ public static string GetName(Type eventSourceType) ArgumentNullException.ThrowIfNull(eventSourceType); - byte[]? manifestBytes = CreateManifestAndDescriptors(eventSourceType, assemblyPathToIncludeInManifest, null, flags); + byte[]? manifestBytes = EventSourceHelpers.CreateManifestAndDescriptors(eventSourceType, assemblyPathToIncludeInManifest, null, flags); return (manifestBytes == null) ? null : Encoding.UTF8.GetString(manifestBytes, 0, manifestBytes.Length); } @@ -1757,7 +1758,7 @@ private static string GetName(Type eventSourceType, EventManifestOptions flags) { ArgumentNullException.ThrowIfNull(eventSourceType); - EventSourceAttribute? attrib = (EventSourceAttribute?)GetCustomAttributeHelper(eventSourceType, typeof(EventSourceAttribute), flags); + EventSourceAttribute? attrib = (EventSourceAttribute?)EventSourceHelpers.GetCustomAttributeHelper(eventSourceType, typeof(EventSourceAttribute), flags); if (attrib != null && attrib.Name != null) return attrib.Name; @@ -2891,7 +2892,7 @@ private void EnsureDescriptorsInitialized() { // get the metadata via reflection. Debug.Assert(m_rawManifest == null); - m_rawManifest = CreateManifestAndDescriptors(this.GetType(), Name, this); + m_rawManifest = EventSourceHelpers.CreateManifestAndDescriptors(this.GetType(), Name, this); Debug.Assert(m_eventData != null); // TODO Enforce singleton pattern @@ -3030,447 +3031,443 @@ internal static bool IsCustomAttributeDefinedHelper( return false; } - // Helper to deal with the fact that the type we are reflecting over might be loaded in the ReflectionOnly context. - // When that is the case, we have the build the custom assemblies on a member by hand. - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2114:ReflectionToDynamicallyAccessedMembers", - Justification = "EnsureDescriptorsInitialized's use of GetType preserves this method which " + - "has dynamically accessed members requirements, but EnsureDescriptorsInitialized does not "+ - "access this member and is safe to call.")] - internal static Attribute? GetCustomAttributeHelper( - MemberInfo member, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties)] - Type attributeType, - EventManifestOptions flags = EventManifestOptions.None) + private static class EventSourceHelpers { - Debug.Assert(attributeType == typeof(EventAttribute) || attributeType == typeof(EventSourceAttribute)); - // AllowEventSourceOverride is an option that allows either Microsoft.Diagnostics.Tracing or - // System.Diagnostics.Tracing EventSource to be considered valid. This should not mattter anywhere but in Microsoft.Diagnostics.Tracing (nuget package). - if (!member.Module.Assembly.ReflectionOnly && (flags & EventManifestOptions.AllowEventSourceOverride) == 0) + // Use reflection to look at the attributes of a class, and generate a manifest for it (as UTF8) and + // return the UTF8 bytes. It also sets up the code:EventData structures needed to dispatch events + // at run time. 'source' is the event source to place the descriptors. If it is null, + // then the descriptors are not created, and just the manifest is generated. + public static byte[]? CreateManifestAndDescriptors( + [DynamicallyAccessedMembers(ManifestMemberTypes)] + Type eventSourceType, + string? eventSourceDllName, + EventSource? source, + EventManifestOptions flags = EventManifestOptions.None) { - // Let the runtime do the work for us, since we can execute code in this context. - return member.GetCustomAttribute(attributeType, inherit: false); - } - - foreach (CustomAttributeData data in CustomAttributeData.GetCustomAttributes(member)) - { - if (AttributeTypeNamesMatch(attributeType, data.Constructor.ReflectedType!)) - { - Attribute? attr = null; + ManifestBuilder? manifest = null; + bool bNeedsManifest = source != null ? !source.SelfDescribingEvents : true; + Exception? exception = null; // exception that might get raised during validation b/c we couldn't/didn't recover from a previous error + byte[]? res = null; - Debug.Assert(data.ConstructorArguments.Count <= 1); + if (eventSourceType.IsAbstract && (flags & EventManifestOptions.Strict) == 0) + return null; - if (data.ConstructorArguments.Count == 1) + try + { + MethodInfo[] methods = eventSourceType.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); + EventAttribute defaultEventAttribute; + int eventId = 1; // The number given to an event that does not have a explicitly given ID. + Dictionary? eventData = null; + Dictionary? eventsByName = null; + if (source != null || (flags & EventManifestOptions.Strict) != 0) { - attr = (Attribute?)Activator.CreateInstance(attributeType, [data.ConstructorArguments[0].Value]); + eventData = new Dictionary(); + ref EventMetadata newEventMetadata = ref CollectionsMarshal.GetValueRefOrAddDefault(eventData, 0, out _); + newEventMetadata.Name = ""; // Event 0 is the 'write messages string' event, and has an empty name. } - else if (data.ConstructorArguments.Count == 0) + + // See if we have localization information. + ResourceManager? resources = null; + EventSourceAttribute? eventSourceAttrib = (EventSourceAttribute?)GetCustomAttributeHelper(eventSourceType, typeof(EventSourceAttribute), flags); + if (eventSourceAttrib != null && eventSourceAttrib.LocalizationResources != null) + resources = new ResourceManager(eventSourceAttrib.LocalizationResources, eventSourceType.Assembly); + + if (source?.GetType() == typeof(NativeRuntimeEventSource)) { - attr = (Attribute?)Activator.CreateInstance(attributeType); + // Don't emit nor generate the manifest for NativeRuntimeEventSource i.e., Microsoft-Windows-DotNETRuntime. + manifest = new ManifestBuilder(resources, flags); + bNeedsManifest = false; } - - if (attr != null) + else { - foreach (CustomAttributeNamedArgument namedArgument in data.NamedArguments) - { - PropertyInfo p = attributeType.GetProperty(namedArgument.MemberInfo.Name, BindingFlags.Public | BindingFlags.Instance)!; - object value = namedArgument.TypedValue.Value!; + // Try to get name and GUID directly from the source. Otherwise get it from the Type's attribute. + string providerName = source?.Name ?? GetName(eventSourceType, flags); + Guid providerGuid = source?.Guid ?? GetGuid(eventSourceType); - if (p.PropertyType.IsEnum) - { - string val = value.ToString()!; - value = Enum.Parse(p.PropertyType, val); - } - - p.SetValue(attr, value, null); - } - - return attr; + manifest = new ManifestBuilder(providerName, providerGuid, eventSourceDllName, resources, flags); } - } - } - - return null; - } - - /// - /// Evaluates if two related "EventSource"-domain types should be considered the same - /// - /// The attribute type in the load context - it's associated with the running - /// EventSource type. This type may be different fromt he base type of the user-defined EventSource. - /// The attribute type in the reflection context - it's associated with - /// the user-defined EventSource, and is in the same assembly as the eventSourceType passed to - /// - /// True - if the types should be considered equivalent, False - otherwise - private static bool AttributeTypeNamesMatch(Type attributeType, Type reflectedAttributeType) - { - return - // are these the same type? - attributeType == reflectedAttributeType || - // are the full typenames equal? - string.Equals(attributeType.FullName, reflectedAttributeType.FullName, StringComparison.Ordinal) || - // are the typenames equal and the namespaces under "Diagnostics.Tracing" (typically - // either Microsoft.Diagnostics.Tracing or System.Diagnostics.Tracing)? - string.Equals(attributeType.Name, reflectedAttributeType.Name, StringComparison.Ordinal) && - attributeType.Namespace!.EndsWith("Diagnostics.Tracing", StringComparison.Ordinal) && - reflectedAttributeType.Namespace!.EndsWith("Diagnostics.Tracing", StringComparison.Ordinal); - } - - private static Type? GetEventSourceBaseType(Type eventSourceType, bool allowEventSourceOverride, bool reflectionOnly) - { - Type? ret = eventSourceType; - - // return false for "object" and interfaces - if (ret.BaseType == null) - return null; - - // now go up the inheritance chain until hitting a concrete type ("object" at worse) - do - { - ret = ret.BaseType; - } - while (ret != null && ret.IsAbstract); - - if (ret != null) - { - if (!allowEventSourceOverride) - { - if (reflectionOnly && ret.FullName != typeof(EventSource).FullName || - !reflectionOnly && ret != typeof(EventSource)) - return null; - } - else - { - if (ret.Name != "EventSource") - return null; - } - } - return ret; - } - - // Use reflection to look at the attributes of a class, and generate a manifest for it (as UTF8) and - // return the UTF8 bytes. It also sets up the code:EventData structures needed to dispatch events - // at run time. 'source' is the event source to place the descriptors. If it is null, - // then the descriptors are not created, and just the manifest is generated. - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2114:ReflectionToDynamicallyAccessedMembers", - Justification = "EnsureDescriptorsInitialized's use of GetType preserves this method which " + - "has dynamically accessed members requirements, but its use of this method satisfies " + - "these requirements because it passes in the result of GetType with the same annotations.")] - private static byte[]? CreateManifestAndDescriptors( - [DynamicallyAccessedMembers(ManifestMemberTypes)] - Type eventSourceType, - string? eventSourceDllName, - EventSource? source, - EventManifestOptions flags = EventManifestOptions.None) - { - ManifestBuilder? manifest = null; - bool bNeedsManifest = source != null ? !source.SelfDescribingEvents : true; - Exception? exception = null; // exception that might get raised during validation b/c we couldn't/didn't recover from a previous error - byte[]? res = null; - - if (eventSourceType.IsAbstract && (flags & EventManifestOptions.Strict) == 0) - return null; - - try - { - MethodInfo[] methods = eventSourceType.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); - EventAttribute defaultEventAttribute; - int eventId = 1; // The number given to an event that does not have a explicitly given ID. - Dictionary? eventData = null; - Dictionary? eventsByName = null; - if (source != null || (flags & EventManifestOptions.Strict) != 0) - { - eventData = new Dictionary(); - ref EventMetadata newEventMetadata = ref CollectionsMarshal.GetValueRefOrAddDefault(eventData, 0, out _); - newEventMetadata.Name = ""; // Event 0 is the 'write messages string' event, and has an empty name. - } - - // See if we have localization information. - ResourceManager? resources = null; - EventSourceAttribute? eventSourceAttrib = (EventSourceAttribute?)GetCustomAttributeHelper(eventSourceType, typeof(EventSourceAttribute), flags); - if (eventSourceAttrib != null && eventSourceAttrib.LocalizationResources != null) - resources = new ResourceManager(eventSourceAttrib.LocalizationResources, eventSourceType.Assembly); - if (source?.GetType() == typeof(NativeRuntimeEventSource)) - { - // Don't emit nor generate the manifest for NativeRuntimeEventSource i.e., Microsoft-Windows-DotNETRuntime. - manifest = new ManifestBuilder(resources, flags); - bNeedsManifest = false; - } - else - { - // Try to get name and GUID directly from the source. Otherwise get it from the Type's attribute. - string providerName = source?.Name ?? GetName(eventSourceType, flags); - Guid providerGuid = source?.Guid ?? GetGuid(eventSourceType); - - manifest = new ManifestBuilder(providerName, providerGuid, eventSourceDllName, resources, flags); - } - - // Add an entry unconditionally for event ID 0 which will be for a string message. - manifest.StartEvent("EventSourceMessage", new EventAttribute(0) { Level = EventLevel.LogAlways, Task = (EventTask)0xFFFE }); - manifest.AddEventParameter(typeof(string), "message"); - manifest.EndEvent(); - - // eventSourceType must be sealed and must derive from this EventSource - if ((flags & EventManifestOptions.Strict) != 0) - { - bool typeMatch = GetEventSourceBaseType(eventSourceType, (flags & EventManifestOptions.AllowEventSourceOverride) != 0, eventSourceType.Assembly.ReflectionOnly) != null; + // Add an entry unconditionally for event ID 0 which will be for a string message. + manifest.StartEvent("EventSourceMessage", new EventAttribute(0) { Level = EventLevel.LogAlways, Task = (EventTask)0xFFFE }); + manifest.AddEventParameter(typeof(string), "message"); + manifest.EndEvent(); - if (!typeMatch) + // eventSourceType must be sealed and must derive from this EventSource + if ((flags & EventManifestOptions.Strict) != 0) { - manifest.ManifestError(SR.EventSource_TypeMustDeriveFromEventSource); - } - if (!eventSourceType.IsAbstract && !eventSourceType.IsSealed) - { - manifest.ManifestError(SR.EventSource_TypeMustBeSealedOrAbstract); - } - } + bool typeMatch = GetEventSourceBaseType(eventSourceType, (flags & EventManifestOptions.AllowEventSourceOverride) != 0, eventSourceType.Assembly.ReflectionOnly) != null; - // Collect task, opcode, keyword and channel information - foreach (string providerEnumKind in (ReadOnlySpan)["Keywords", "Tasks", "Opcodes"]) - { - Type? nestedType = eventSourceType.GetNestedType(providerEnumKind); - if (nestedType != null) - { - if (eventSourceType.IsAbstract) + if (!typeMatch) { - manifest.ManifestError(SR.Format(SR.EventSource_AbstractMustNotDeclareKTOC, nestedType.Name)); + manifest.ManifestError(SR.EventSource_TypeMustDeriveFromEventSource); } - else + if (!eventSourceType.IsAbstract && !eventSourceType.IsSealed) { - foreach (FieldInfo staticField in nestedType.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)) - { - AddProviderEnumKind(manifest, staticField, providerEnumKind); - } + manifest.ManifestError(SR.EventSource_TypeMustBeSealedOrAbstract); } } - } - // ensure we have keywords for the session-filtering reserved bits - { - manifest.AddKeyword("Session3", (long)0x1000 << 32); - manifest.AddKeyword("Session2", (long)0x2000 << 32); - manifest.AddKeyword("Session1", (long)0x4000 << 32); - manifest.AddKeyword("Session0", (long)0x8000 << 32); - } - if (eventSourceType != typeof(EventSource)) - { - for (int i = 0; i < methods.Length; i++) + // Collect task, opcode, keyword and channel information + foreach (string providerEnumKind in (ReadOnlySpan)["Keywords", "Tasks", "Opcodes"]) { - MethodInfo method = methods[i]; - - // Compat: until v4.5.1 we ignored any non-void returning methods as well as virtual methods for - // the only reason of limiting the number of methods considered to be events. This broke a common - // design of having event sources implement specific interfaces. To fix this in a compatible way - // we will now allow both non-void returning and virtual methods to be Event methods, as long - // as they are marked with the [Event] attribute - if (/* method.IsVirtual || */ method.IsStatic) + Type? nestedType = eventSourceType.GetNestedType(providerEnumKind); + if (nestedType != null) { - continue; - } - - // Get the EventDescriptor (from the Custom attributes) - EventAttribute? eventAttribute = (EventAttribute?)GetCustomAttributeHelper(method, typeof(EventAttribute), flags); - - if (eventSourceType.IsAbstract) - { - if (eventAttribute != null) + if (eventSourceType.IsAbstract) { - manifest.ManifestError(SR.Format(SR.EventSource_AbstractMustNotDeclareEventMethods, method.Name, eventAttribute.EventId)); + manifest.ManifestError(SR.Format(SR.EventSource_AbstractMustNotDeclareKTOC, nestedType.Name)); + } + else + { + foreach (FieldInfo staticField in nestedType.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)) + { + AddProviderEnumKind(manifest, staticField, providerEnumKind); + } } - continue; } - else if (eventAttribute == null) + } + // ensure we have keywords for the session-filtering reserved bits + { + manifest.AddKeyword("Session3", (long)0x1000 << 32); + manifest.AddKeyword("Session2", (long)0x2000 << 32); + manifest.AddKeyword("Session1", (long)0x4000 << 32); + manifest.AddKeyword("Session0", (long)0x8000 << 32); + } + + if (eventSourceType != typeof(EventSource)) + { + for (int i = 0; i < methods.Length; i++) { - // Methods that don't return void can't be events, if they're NOT marked with [Event]. - // (see Compat comment above) - if (method.ReturnType != typeof(void)) + MethodInfo method = methods[i]; + + // Compat: until v4.5.1 we ignored any non-void returning methods as well as virtual methods for + // the only reason of limiting the number of methods considered to be events. This broke a common + // design of having event sources implement specific interfaces. To fix this in a compatible way + // we will now allow both non-void returning and virtual methods to be Event methods, as long + // as they are marked with the [Event] attribute + if (/* method.IsVirtual || */ method.IsStatic) { continue; } - // Continue to ignore virtual methods if they do NOT have the [Event] attribute - // (see Compat comment above) - if (method.IsVirtual) + // Get the EventDescriptor (from the Custom attributes) + EventAttribute? eventAttribute = (EventAttribute?)GetCustomAttributeHelper(method, typeof(EventAttribute), flags); + + if (eventSourceType.IsAbstract) { + if (eventAttribute != null) + { + manifest.ManifestError(SR.Format(SR.EventSource_AbstractMustNotDeclareEventMethods, method.Name, eventAttribute.EventId)); + } continue; } + else if (eventAttribute == null) + { + // Methods that don't return void can't be events, if they're NOT marked with [Event]. + // (see Compat comment above) + if (method.ReturnType != typeof(void)) + { + continue; + } - // If we explicitly mark the method as not being an event, then honor that. - if (IsCustomAttributeDefinedHelper(method, typeof(NonEventAttribute), flags)) - continue; + // Continue to ignore virtual methods if they do NOT have the [Event] attribute + // (see Compat comment above) + if (method.IsVirtual) + { + continue; + } - defaultEventAttribute = new EventAttribute(eventId); - eventAttribute = defaultEventAttribute; - } - else if (eventAttribute.EventId <= 0) - { - manifest.ManifestError(SR.EventSource_NeedPositiveId, true); - continue; // don't validate anything else for this event - } - if (method.Name.LastIndexOf('.') >= 0) - { - manifest.ManifestError(SR.Format(SR.EventSource_EventMustNotBeExplicitImplementation, method.Name, eventAttribute.EventId)); - } + // If we explicitly mark the method as not being an event, then honor that. + if (IsCustomAttributeDefinedHelper(method, typeof(NonEventAttribute), flags)) + continue; - eventId++; - string eventName = method.Name; + defaultEventAttribute = new EventAttribute(eventId); + eventAttribute = defaultEventAttribute; + } + else if (eventAttribute.EventId <= 0) + { + manifest.ManifestError(SR.EventSource_NeedPositiveId, true); + continue; // don't validate anything else for this event + } + if (method.Name.LastIndexOf('.') >= 0) + { + manifest.ManifestError(SR.Format(SR.EventSource_EventMustNotBeExplicitImplementation, method.Name, eventAttribute.EventId)); + } - if (eventAttribute.Opcode == EventOpcode.Info) // We are still using the default opcode. - { - // By default pick a task ID derived from the EventID, starting with the highest task number and working back - bool noTask = (eventAttribute.Task == EventTask.None); - if (noTask) - eventAttribute.Task = (EventTask)(0xFFFE - eventAttribute.EventId); - - // Unless we explicitly set the opcode to Info (to override the auto-generate of Start or Stop opcodes, - // pick a default opcode based on the event name (either Info or start or stop if the name ends with that suffix). - if (!eventAttribute.IsOpcodeSet) - eventAttribute.Opcode = GetOpcodeWithDefault(EventOpcode.Info, eventName); - - // Make the stop opcode have the same task as the start opcode. - if (noTask) + eventId++; + string eventName = method.Name; + + if (eventAttribute.Opcode == EventOpcode.Info) // We are still using the default opcode. { - if (eventAttribute.Opcode == EventOpcode.Start) + // By default pick a task ID derived from the EventID, starting with the highest task number and working back + bool noTask = (eventAttribute.Task == EventTask.None); + if (noTask) + eventAttribute.Task = (EventTask)(0xFFFE - eventAttribute.EventId); + + // Unless we explicitly set the opcode to Info (to override the auto-generate of Start or Stop opcodes, + // pick a default opcode based on the event name (either Info or start or stop if the name ends with that suffix). + if (!eventAttribute.IsOpcodeSet) + eventAttribute.Opcode = GetOpcodeWithDefault(EventOpcode.Info, eventName); + + // Make the stop opcode have the same task as the start opcode. + if (noTask) { - if (eventName.EndsWith(ActivityStartSuffix, StringComparison.Ordinal)) + if (eventAttribute.Opcode == EventOpcode.Start) { - string taskName = eventName[..^ActivityStartSuffix.Length]; // Remove the Start suffix to get the task name + if (eventName.EndsWith(ActivityStartSuffix, StringComparison.Ordinal)) + { + string taskName = eventName[..^ActivityStartSuffix.Length]; // Remove the Start suffix to get the task name - // Add a task that is just the task name for the start event. This suppress the auto-task generation - // That would otherwise happen (and create 'TaskName'Start as task name rather than just 'TaskName' - manifest.AddTask(taskName, (int)eventAttribute.Task); + // Add a task that is just the task name for the start event. This suppress the auto-task generation + // That would otherwise happen (and create 'TaskName'Start as task name rather than just 'TaskName' + manifest.AddTask(taskName, (int)eventAttribute.Task); + } } - } - else if (eventAttribute.Opcode == EventOpcode.Stop) - { - // Find the start associated with this stop event. We require start to be immediately before the stop - int startEventId = eventAttribute.EventId - 1; - Debug.Assert(0 <= startEventId); - if (eventData != null) + else if (eventAttribute.Opcode == EventOpcode.Stop) { - ref EventMetadata startEventMetadata = ref CollectionsMarshal.GetValueRefOrNullRef(eventData, startEventId); - if (!Unsafe.IsNullRef(ref startEventMetadata)) + // Find the start associated with this stop event. We require start to be immediately before the stop + int startEventId = eventAttribute.EventId - 1; + Debug.Assert(0 <= startEventId); + if (eventData != null) { - // If you remove the Stop and add a Start does that name match the Start Event's Name? - // Ideally we would throw an error - if (startEventMetadata.Descriptor.Opcode == (byte)EventOpcode.Start && - startEventMetadata.Name.EndsWith(ActivityStartSuffix, StringComparison.Ordinal) && - eventName.EndsWith(ActivityStopSuffix, StringComparison.Ordinal) && - startEventMetadata.Name.AsSpan()[..^ActivityStartSuffix.Length].SequenceEqual( - eventName.AsSpan()[..^ActivityStopSuffix.Length])) + ref EventMetadata startEventMetadata = ref CollectionsMarshal.GetValueRefOrNullRef(eventData, startEventId); + if (!Unsafe.IsNullRef(ref startEventMetadata)) { - // Make the stop event match the start event - eventAttribute.Task = (EventTask)startEventMetadata.Descriptor.Task; - noTask = false; + // If you remove the Stop and add a Start does that name match the Start Event's Name? + // Ideally we would throw an error + if (startEventMetadata.Descriptor.Opcode == (byte)EventOpcode.Start && + startEventMetadata.Name.EndsWith(ActivityStartSuffix, StringComparison.Ordinal) && + eventName.EndsWith(ActivityStopSuffix, StringComparison.Ordinal) && + startEventMetadata.Name.AsSpan()[..^ActivityStartSuffix.Length].SequenceEqual( + eventName.AsSpan()[..^ActivityStopSuffix.Length])) + { + // Make the stop event match the start event + eventAttribute.Task = (EventTask)startEventMetadata.Descriptor.Task; + noTask = false; + } } } - } - if (noTask && (flags & EventManifestOptions.Strict) != 0) // Throw an error if we can compatibly. - { - throw new ArgumentException(SR.EventSource_StopsFollowStarts); + if (noTask && (flags & EventManifestOptions.Strict) != 0) // Throw an error if we can compatibly. + { + throw new ArgumentException(SR.EventSource_StopsFollowStarts); + } } } } - } - ParameterInfo[] args = method.GetParameters(); + ParameterInfo[] args = method.GetParameters(); - bool hasRelatedActivityID = RemoveFirstArgIfRelatedActivityId(ref args); - if (!(source != null && source.SelfDescribingEvents)) - { - manifest.StartEvent(eventName, eventAttribute); - for (int fieldIdx = 0; fieldIdx < args.Length; fieldIdx++) + bool hasRelatedActivityID = RemoveFirstArgIfRelatedActivityId(ref args); + if (!(source != null && source.SelfDescribingEvents)) { - manifest.AddEventParameter(args[fieldIdx].ParameterType, args[fieldIdx].Name!); + manifest.StartEvent(eventName, eventAttribute); + for (int fieldIdx = 0; fieldIdx < args.Length; fieldIdx++) + { + manifest.AddEventParameter(args[fieldIdx].ParameterType, args[fieldIdx].Name!); + } + manifest.EndEvent(); } - manifest.EndEvent(); - } - - if (source != null || (flags & EventManifestOptions.Strict) != 0) - { - Debug.Assert(eventData != null); - // Do checking for user errors (optional, but not a big deal so we do it). - DebugCheckEvent(ref eventsByName, eventData, method, eventAttribute, manifest, flags); - // add the channel keyword for Event Viewer channel based filters. This is added for creating the EventDescriptors only - // and is not required for the manifest - if (eventAttribute.Channel != EventChannel.None) + if (source != null || (flags & EventManifestOptions.Strict) != 0) { - unchecked + Debug.Assert(eventData != null); + // Do checking for user errors (optional, but not a big deal so we do it). + DebugCheckEvent(ref eventsByName, eventData, method, eventAttribute, manifest, flags); + + // add the channel keyword for Event Viewer channel based filters. This is added for creating the EventDescriptors only + // and is not required for the manifest + if (eventAttribute.Channel != EventChannel.None) { - eventAttribute.Keywords |= (EventKeywords)manifest.GetChannelKeyword(eventAttribute.Channel, (ulong)eventAttribute.Keywords); + unchecked + { + eventAttribute.Keywords |= (EventKeywords)manifest.GetChannelKeyword(eventAttribute.Channel, (ulong)eventAttribute.Keywords); + } } - } - if (manifest.HasResources) - { - string eventKey = "event_" + eventName; - if (manifest.GetLocalizedMessage(eventKey, CultureInfo.CurrentUICulture, etwFormat: false) is string msg) + if (manifest.HasResources) { - // overwrite inline message with the localized message - eventAttribute.Message = msg; + string eventKey = "event_" + eventName; + if (manifest.GetLocalizedMessage(eventKey, CultureInfo.CurrentUICulture, etwFormat: false) is string msg) + { + // overwrite inline message with the localized message + eventAttribute.Message = msg; + } } - } - AddEventDescriptor(ref eventData, eventName, eventAttribute, args, hasRelatedActivityID); + AddEventDescriptor(ref eventData, eventName, eventAttribute, args, hasRelatedActivityID); + } } } - } - // Tell the TraceLogging stuff where to start allocating its own IDs. - NameInfo.ReserveEventIDsBelow(eventId); + // Tell the TraceLogging stuff where to start allocating its own IDs. + NameInfo.ReserveEventIDsBelow(eventId); + + if (source != null) + { + Debug.Assert(eventData != null); + source.m_eventData = eventData; // officially initialize it. We do this at most once (it is racy otherwise). + source.m_channelData = manifest.GetChannelData(); + } - if (source != null) + // if this is an abstract event source we've already performed all the validation we can + if (!eventSourceType.IsAbstract && (source == null || !source.SelfDescribingEvents)) + { + bNeedsManifest = (flags & EventManifestOptions.OnlyIfNeededForRegistration) == 0 || manifest.GetChannelData().Length > 0; + + // if the manifest is not needed and we're not requested to validate the event source return early + if (!bNeedsManifest && (flags & EventManifestOptions.Strict) == 0) + return null; + + res = manifest.CreateManifest(); + res = (res.Length > 0) ? res : null; + } + } + catch (Exception e) { - Debug.Assert(eventData != null); - source.m_eventData = eventData; // officially initialize it. We do this at most once (it is racy otherwise). - source.m_channelData = manifest.GetChannelData(); + // if this is a runtime manifest generation let the exception propagate + if ((flags & EventManifestOptions.Strict) == 0) + throw; + // else store it to include it in the Argument exception we raise below + exception = e; } - // if this is an abstract event source we've already performed all the validation we can - if (!eventSourceType.IsAbstract && (source == null || !source.SelfDescribingEvents)) + if ((flags & EventManifestOptions.Strict) != 0 && (manifest?.Errors.Count > 0 || exception != null)) { - bNeedsManifest = (flags & EventManifestOptions.OnlyIfNeededForRegistration) == 0 || manifest.GetChannelData().Length > 0; + string msg = string.Empty; - // if the manifest is not needed and we're not requested to validate the event source return early - if (!bNeedsManifest && (flags & EventManifestOptions.Strict) == 0) - return null; + if (manifest?.Errors.Count > 0) + { + bool firstError = true; + foreach (string error in manifest.Errors) + { + if (!firstError) + msg += Environment.NewLine; + firstError = false; + msg += error; + } + } + else + msg = "Unexpected error: " + exception!.Message; - res = manifest.CreateManifest(); - res = (res.Length > 0) ? res : null; + throw new ArgumentException(msg, exception); } + + return bNeedsManifest ? res : null; } - catch (Exception e) - { - // if this is a runtime manifest generation let the exception propagate - if ((flags & EventManifestOptions.Strict) == 0) - throw; - // else store it to include it in the Argument exception we raise below - exception = e; - } - if ((flags & EventManifestOptions.Strict) != 0 && (manifest?.Errors.Count > 0 || exception != null)) + + // Helper to deal with the fact that the type we are reflecting over might be loaded in the ReflectionOnly context. + // When that is the case, we have the build the custom assemblies on a member by hand. + internal static Attribute? GetCustomAttributeHelper( + MemberInfo member, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties)] + Type attributeType, + EventManifestOptions flags = EventManifestOptions.None) { - string msg = string.Empty; + Debug.Assert(attributeType == typeof(EventAttribute) || attributeType == typeof(EventSourceAttribute)); + // AllowEventSourceOverride is an option that allows either Microsoft.Diagnostics.Tracing or + // System.Diagnostics.Tracing EventSource to be considered valid. This should not mattter anywhere but in Microsoft.Diagnostics.Tracing (nuget package). + if (!member.Module.Assembly.ReflectionOnly && (flags & EventManifestOptions.AllowEventSourceOverride) == 0) + { + // Let the runtime do the work for us, since we can execute code in this context. + return member.GetCustomAttribute(attributeType, inherit: false); + } - if (manifest?.Errors.Count > 0) + foreach (CustomAttributeData data in CustomAttributeData.GetCustomAttributes(member)) { - bool firstError = true; - foreach (string error in manifest.Errors) + if (AttributeTypeNamesMatch(attributeType, data.Constructor.ReflectedType!)) { - if (!firstError) - msg += Environment.NewLine; - firstError = false; - msg += error; + Attribute? attr = null; + + Debug.Assert(data.ConstructorArguments.Count <= 1); + + if (data.ConstructorArguments.Count == 1) + { + attr = (Attribute?)Activator.CreateInstance(attributeType, [data.ConstructorArguments[0].Value]); + } + else if (data.ConstructorArguments.Count == 0) + { + attr = (Attribute?)Activator.CreateInstance(attributeType); + } + + if (attr != null) + { + foreach (CustomAttributeNamedArgument namedArgument in data.NamedArguments) + { + PropertyInfo p = attributeType.GetProperty(namedArgument.MemberInfo.Name, BindingFlags.Public | BindingFlags.Instance)!; + object value = namedArgument.TypedValue.Value!; + + if (p.PropertyType.IsEnum) + { + string val = value.ToString()!; + value = Enum.Parse(p.PropertyType, val); + } + + p.SetValue(attr, value, null); + } + + return attr; + } } } - else - msg = "Unexpected error: " + exception!.Message; - throw new ArgumentException(msg, exception); + return null; + } + } + + /// + /// Evaluates if two related "EventSource"-domain types should be considered the same + /// + /// The attribute type in the load context - it's associated with the running + /// EventSource type. This type may be different fromt he base type of the user-defined EventSource. + /// The attribute type in the reflection context - it's associated with + /// the user-defined EventSource, and is in the same assembly as the eventSourceType passed to + /// + /// True - if the types should be considered equivalent, False - otherwise + private static bool AttributeTypeNamesMatch(Type attributeType, Type reflectedAttributeType) + { + return + // are these the same type? + attributeType == reflectedAttributeType || + // are the full typenames equal? + string.Equals(attributeType.FullName, reflectedAttributeType.FullName, StringComparison.Ordinal) || + // are the typenames equal and the namespaces under "Diagnostics.Tracing" (typically + // either Microsoft.Diagnostics.Tracing or System.Diagnostics.Tracing)? + string.Equals(attributeType.Name, reflectedAttributeType.Name, StringComparison.Ordinal) && + attributeType.Namespace!.EndsWith("Diagnostics.Tracing", StringComparison.Ordinal) && + reflectedAttributeType.Namespace!.EndsWith("Diagnostics.Tracing", StringComparison.Ordinal); + } + + private static Type? GetEventSourceBaseType(Type eventSourceType, bool allowEventSourceOverride, bool reflectionOnly) + { + Type? ret = eventSourceType; + + // return false for "object" and interfaces + if (ret.BaseType == null) + return null; + + // now go up the inheritance chain until hitting a concrete type ("object" at worse) + do + { + ret = ret.BaseType; } + while (ret != null && ret.IsAbstract); - return bNeedsManifest ? res : null; + if (ret != null) + { + if (!allowEventSourceOverride) + { + if (reflectionOnly && ret.FullName != typeof(EventSource).FullName || + !reflectionOnly && ret != typeof(EventSource)) + return null; + } + else + { + if (ret.Name != "EventSource") + return null; + } + } + return ret; } private static bool RemoveFirstArgIfRelatedActivityId(ref ParameterInfo[] args)