diff --git a/src/libraries/System.Private.CoreLib/gen/EventSourceGenerator.Emitter.cs b/src/libraries/System.Private.CoreLib/gen/EventSourceGenerator.Emitter.cs index 894ae45816ad38..2dcedb1b435ce8 100644 --- a/src/libraries/System.Private.CoreLib/gen/EventSourceGenerator.Emitter.cs +++ b/src/libraries/System.Private.CoreLib/gen/EventSourceGenerator.Emitter.cs @@ -12,97 +12,86 @@ namespace Generators { public partial class EventSourceGenerator { - private sealed class Emitter - { - private readonly StringBuilder _builder = new StringBuilder(1024); - private readonly GeneratorExecutionContext _context; - - public Emitter(GeneratorExecutionContext context) => _context = context; - - public void Emit(EventSourceClass[] eventSources, CancellationToken cancellationToken) - { - foreach (EventSourceClass? ec in eventSources) - { - if (cancellationToken.IsCancellationRequested) - { - // stop any additional work - break; - } + /// Code for a [GeneratedCode] attribute to put on the top-level generated members. + private static readonly string s_generatedCodeAttribute = $"[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"{typeof(EventSourceGenerator).Assembly.GetName().Name}\", \"{typeof(EventSourceGenerator).Assembly.GetName().Version}\")]"; - _builder.AppendLine("using System;"); - GenType(ec); + private static void EmitSourceFile(SourceProductionContext context, EventSourceClass ec) + { + StringBuilder sb = new StringBuilder(1024); - _context.AddSource($"{ec.ClassName}.g.cs", SourceText.From(_builder.ToString(), Encoding.UTF8)); + sb.AppendLine(@"// "); + sb.AppendLine(); + sb.AppendLine("using System;"); + GenType(ec, sb); - _builder.Clear(); - } - } + context.AddSource($"{ec.ClassName}.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8)); + } - private void GenType(EventSourceClass ec) + private static void GenType(EventSourceClass ec, StringBuilder sb) + { + if (!string.IsNullOrWhiteSpace(ec.Namespace)) { - if (!string.IsNullOrWhiteSpace(ec.Namespace)) - { - _builder.AppendLine($@" + sb.AppendLine($@" namespace {ec.Namespace} {{"); - } + } - _builder.AppendLine($@" + sb.AppendLine($@" + {s_generatedCodeAttribute} partial class {ec.ClassName} {{"); - GenerateConstructor(ec); + GenerateConstructor(ec, sb); - GenerateProviderMetadata(ec.SourceName); + GenerateProviderMetadata(ec.SourceName, sb); - _builder.AppendLine($@" + sb.AppendLine($@" }}"); - if (!string.IsNullOrWhiteSpace(ec.Namespace)) - { - _builder.AppendLine($@" + if (!string.IsNullOrWhiteSpace(ec.Namespace)) + { + sb.AppendLine($@" }}"); - } } + } - private void GenerateConstructor(EventSourceClass ec) - { - _builder.AppendLine($@" + private static void GenerateConstructor(EventSourceClass ec, StringBuilder sb) + { + sb.AppendLine($@" private {ec.ClassName}() : base(new Guid({ec.Guid.ToString("x").Replace("{", "").Replace("}", "")}), ""{ec.SourceName}"") {{ }}"); - } + } - private void GenerateProviderMetadata(string sourceName) - { - _builder.Append(@" + private static void GenerateProviderMetadata(string sourceName, StringBuilder sb) + { + sb.Append(@" private protected override ReadOnlySpan ProviderMetadata => new byte[] { "); - byte[] metadataBytes = MetadataForString(sourceName); - foreach (byte b in metadataBytes) - { - _builder.Append($"0x{b:x}, "); - } - - _builder.AppendLine(@"};"); - } - - // From System.Private.CoreLib - private static byte[] MetadataForString(string name) + byte[] metadataBytes = MetadataForString(sourceName); + foreach (byte b in metadataBytes) { - CheckName(name); - int metadataSize = Encoding.UTF8.GetByteCount(name) + 3; - byte[]? metadata = new byte[metadataSize]; - ushort totalSize = checked((ushort)(metadataSize)); - metadata[0] = unchecked((byte)totalSize); - metadata[1] = unchecked((byte)(totalSize >> 8)); - Encoding.UTF8.GetBytes(name, 0, name.Length, metadata, 2); - return metadata; + sb.Append($"0x{b:x}, "); } - private static void CheckName(string? name) + sb.AppendLine(@"};"); + } + + // From System.Private.CoreLib + private static byte[] MetadataForString(string name) + { + CheckName(name); + int metadataSize = Encoding.UTF8.GetByteCount(name) + 3; + byte[]? metadata = new byte[metadataSize]; + ushort totalSize = checked((ushort)(metadataSize)); + metadata[0] = unchecked((byte)totalSize); + metadata[1] = unchecked((byte)(totalSize >> 8)); + Encoding.UTF8.GetBytes(name, 0, name.Length, metadata, 2); + return metadata; + } + + private static void CheckName(string? name) + { + if (name != null && 0 <= name.IndexOf('\0')) { - if (name != null && 0 <= name.IndexOf('\0')) - { - throw new ArgumentOutOfRangeException(nameof(name)); - } + throw new ArgumentOutOfRangeException(nameof(name)); } } } diff --git a/src/libraries/System.Private.CoreLib/gen/EventSourceGenerator.Parser.cs b/src/libraries/System.Private.CoreLib/gen/EventSourceGenerator.Parser.cs index 2b28b46e03335b..0ea373c9efb82b 100644 --- a/src/libraries/System.Private.CoreLib/gen/EventSourceGenerator.Parser.cs +++ b/src/libraries/System.Private.CoreLib/gen/EventSourceGenerator.Parser.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Security.Cryptography; using System.Text; @@ -10,186 +11,139 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.DotnetRuntime.Extensions; namespace Generators { public partial class EventSourceGenerator { - private sealed class Parser + private static bool IsSyntaxTargetForGeneration(SyntaxNode node, CancellationToken cancellationToken) => + node is ClassDeclarationSyntax { AttributeLists.Count: > 0 }; + + private static EventSourceClass? GetSemanticTargetForGeneration(GeneratorSyntaxContext context, CancellationToken cancellationToken) { - private readonly CancellationToken _cancellationToken; - private readonly Compilation _compilation; - private readonly Action _reportDiagnostic; + const string EventSourceAutoGenerateAttribute = "System.Diagnostics.Tracing.EventSourceAutoGenerateAttribute"; + const string EventSourceAttribute = "System.Diagnostics.Tracing.EventSourceAttribute"; - public Parser(Compilation compilation, Action reportDiagnostic, CancellationToken cancellationToken) - { - _compilation = compilation; - _cancellationToken = cancellationToken; - _reportDiagnostic = reportDiagnostic; - } + var classDef = (ClassDeclarationSyntax)context.Node; + SemanticModel sm = context.SemanticModel; + EventSourceClass? eventSourceClass = null; - public EventSourceClass[] GetEventSourceClasses(List classDeclarations) + bool autoGenerate = false; + foreach (AttributeListSyntax cal in classDef.AttributeLists) { - INamedTypeSymbol? autogenerateAttribute = _compilation.GetBestTypeByMetadataName("System.Diagnostics.Tracing.EventSourceAutoGenerateAttribute"); - if (autogenerateAttribute is null) + foreach (AttributeSyntax ca in cal.Attributes) { - // No EventSourceAutoGenerateAttribute - return Array.Empty(); - } + if (sm.GetSymbolInfo(ca, cancellationToken).Symbol is not IMethodSymbol caSymbol) + { + // badly formed attribute definition, or not the right attribute + continue; + } - INamedTypeSymbol? eventSourceAttribute = _compilation.GetBestTypeByMetadataName("System.Diagnostics.Tracing.EventSourceAttribute"); - if (eventSourceAttribute is null) - { - // No EventSourceAttribute - return Array.Empty(); - } + string attributeFullName = caSymbol.ContainingType.ToDisplayString(); - List? results = null; - // we enumerate by syntax tree, to minimize the need to instantiate semantic models (since they're expensive) - foreach (IGrouping? group in classDeclarations.GroupBy(x => x.SyntaxTree)) - { - SemanticModel? sm = null; - EventSourceClass? eventSourceClass = null; - foreach (ClassDeclarationSyntax? classDef in group) + if (attributeFullName.Equals(EventSourceAutoGenerateAttribute, StringComparison.Ordinal)) + { + autoGenerate = true; + continue; + } + + if (!attributeFullName.Equals(EventSourceAttribute, StringComparison.Ordinal)) + { + continue; + } + + string nspace = string.Empty; + NamespaceDeclarationSyntax? ns = classDef.Parent as NamespaceDeclarationSyntax; + if (ns is null) { - if (_cancellationToken.IsCancellationRequested) + if (classDef.Parent is not CompilationUnitSyntax) { - // be nice and stop if we're asked to - return results?.ToArray() ?? Array.Empty(); + // since this generator doesn't know how to generate a nested type... + continue; } - - bool autoGenerate = false; - foreach (AttributeListSyntax? cal in classDef.AttributeLists) + } + else + { + nspace = ns.Name.ToString(); + while (true) { - foreach (AttributeSyntax? ca in cal.Attributes) + ns = ns.Parent as NamespaceDeclarationSyntax; + if (ns == null) { - // need a semantic model for this tree - sm ??= _compilation.GetSemanticModel(classDef.SyntaxTree); - - if (sm.GetSymbolInfo(ca, _cancellationToken).Symbol is not IMethodSymbol caSymbol) - { - // badly formed attribute definition, or not the right attribute - continue; - } - - if (autogenerateAttribute.Equals(caSymbol.ContainingType, SymbolEqualityComparer.Default)) - { - autoGenerate = true; - continue; - } - if (eventSourceAttribute.Equals(caSymbol.ContainingType, SymbolEqualityComparer.Default)) - { - string nspace = string.Empty; - NamespaceDeclarationSyntax? ns = classDef.Parent as NamespaceDeclarationSyntax; - if (ns is null) - { - if (classDef.Parent is not CompilationUnitSyntax) - { - // since this generator doesn't know how to generate a nested type... - continue; - } - } - else - { - nspace = ns.Name.ToString(); - while (true) - { - ns = ns.Parent as NamespaceDeclarationSyntax; - if (ns == null) - { - break; - } - - nspace = $"{ns.Name}.{nspace}"; - } - } - - string className = classDef.Identifier.ToString(); - string name = className; - string guid = ""; - - SeparatedSyntaxList? args = ca.ArgumentList?.Arguments; - if (args is not null) - { - foreach (AttributeArgumentSyntax? arg in args) - { - string? argName = arg.NameEquals!.Name.Identifier.ToString(); - string? value = sm.GetConstantValue(arg.Expression, _cancellationToken).ToString(); - - switch (argName) - { - case "Guid": - guid = value; - break; - case "Name": - name = value; - break; - } - } - } - - if (!Guid.TryParse(guid, out Guid result)) - { - result = GenerateGuidFromName(name.ToUpperInvariant()); - } - - eventSourceClass = new EventSourceClass - { - Namespace = nspace, - ClassName = className, - SourceName = name, - Guid = result - }; - continue; - } + break; } - } - if (!autoGenerate) - { - continue; + nspace = $"{ns.Name}.{nspace}"; } + } + + string className = classDef.Identifier.ToString(); + string name = className; + string guid = ""; - if (eventSourceClass is null) + SeparatedSyntaxList? args = ca.ArgumentList?.Arguments; + if (args is not null) + { + foreach (AttributeArgumentSyntax arg in args) { - continue; + string argName = arg.NameEquals!.Name.Identifier.ToString(); + string value = sm.GetConstantValue(arg.Expression, cancellationToken).ToString(); + + switch (argName) + { + case "Guid": + guid = value; + break; + case "Name": + name = value; + break; + } } + } - results ??= new List(); - results.Add(eventSourceClass); + if (!Guid.TryParse(guid, out Guid result)) + { + result = GenerateGuidFromName(name.ToUpperInvariant()); } + + eventSourceClass = new EventSourceClass(nspace, className, name, result); + continue; } + } - return results?.ToArray() ?? Array.Empty(); + if (!autoGenerate) + { + return null; } - // From System.Private.CoreLib - private static Guid GenerateGuidFromName(string name) + return eventSourceClass; + } + + // From System.Private.CoreLib + private static Guid GenerateGuidFromName(string name) + { + ReadOnlySpan namespaceBytes = new byte[] // rely on C# compiler optimization to remove byte[] allocation { - ReadOnlySpan namespaceBytes = new byte[] // rely on C# compiler optimization to remove byte[] allocation - { 0x48, 0x2C, 0x2D, 0xB2, 0xC3, 0x90, 0x47, 0xC8, 0x87, 0xF8, 0x1A, 0x15, 0xBF, 0xC1, 0x30, 0xFB, - }; + }; - byte[] bytes = Encoding.BigEndianUnicode.GetBytes(name); + byte[] bytes = Encoding.BigEndianUnicode.GetBytes(name); - byte[] combinedBytes = new byte[namespaceBytes.Length + bytes.Length]; + byte[] combinedBytes = new byte[namespaceBytes.Length + bytes.Length]; - bytes.CopyTo(combinedBytes, namespaceBytes.Length); - namespaceBytes.CopyTo(combinedBytes); + bytes.CopyTo(combinedBytes, namespaceBytes.Length); + namespaceBytes.CopyTo(combinedBytes); - using (SHA1 sha = SHA1.Create()) - { - bytes = sha.ComputeHash(combinedBytes); - } + using (SHA1 sha = SHA1.Create()) + { + bytes = sha.ComputeHash(combinedBytes); + } - Array.Resize(ref bytes, 16); + Array.Resize(ref bytes, 16); - bytes[7] = unchecked((byte)((bytes[7] & 0x0F) | 0x50)); // Set high 4 bits of octet 7 to 5, as per RFC 4122 - return new Guid(bytes); - } + bytes[7] = unchecked((byte)((bytes[7] & 0x0F) | 0x50)); // Set high 4 bits of octet 7 to 5, as per RFC 4122 + return new Guid(bytes); } } } diff --git a/src/libraries/System.Private.CoreLib/gen/EventSourceGenerator.cs b/src/libraries/System.Private.CoreLib/gen/EventSourceGenerator.cs index a86fef7e38ced7..cebe3a5d32c504 100644 --- a/src/libraries/System.Private.CoreLib/gen/EventSourceGenerator.cs +++ b/src/libraries/System.Private.CoreLib/gen/EventSourceGenerator.cs @@ -3,14 +3,15 @@ using System; using System.Collections.Generic; - +using System.Collections.Immutable; +using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Generators { [Generator] - public partial class EventSourceGenerator : ISourceGenerator + public partial class EventSourceGenerator : IIncrementalGenerator { // Example input: // @@ -32,77 +33,16 @@ public partial class EventSourceGenerator : ISourceGenerator // } // } - public void Initialize(GeneratorInitializationContext context) - => context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); - - public void Execute(GeneratorExecutionContext context) - { - SyntaxReceiver? receiver = context.SyntaxReceiver as SyntaxReceiver; - if ((receiver?.CandidateClasses?.Count ?? 0) == 0) - { - // nothing to do yet - return; - } - - Parser? p = new Parser(context.Compilation, context.ReportDiagnostic, context.CancellationToken); - EventSourceClass[]? eventSources = p.GetEventSourceClasses(receiver.CandidateClasses); - - if (eventSources?.Length > 0) - { - Emitter? e = new Emitter(context); - e.Emit(eventSources, context.CancellationToken); - } - } - - private sealed class SyntaxReceiver : ISyntaxReceiver + public void Initialize(IncrementalGeneratorInitializationContext context) { - private List? _candidateClasses; - - public List? CandidateClasses => _candidateClasses; - - public void OnVisitSyntaxNode(SyntaxNode syntaxNode) - { - // Only add classes annotated [EventSourceAutoGenerate] to reduce busy work. - const string EventSourceAttribute = "EventSourceAutoGenerateAttribute"; - const string EventSourceAttributeShort = "EventSourceAutoGenerate"; + IncrementalValuesProvider eventSourceClasses = + context.SyntaxProvider + .CreateSyntaxProvider(IsSyntaxTargetForGeneration, GetSemanticTargetForGeneration) + .Where(x => x is not null); - // Only classes - if (syntaxNode is ClassDeclarationSyntax classDeclaration) - { - // Check if has EventSource attribute before adding to candidates - // as we don't want to add every class in the project - foreach (AttributeListSyntax? cal in classDeclaration.AttributeLists) - { - foreach (AttributeSyntax? ca in cal.Attributes) - { - // Check if Span length matches before allocating the string to check more - int length = ca.Name.Span.Length; - if (length != EventSourceAttribute.Length && length != EventSourceAttributeShort.Length) - { - continue; - } - - // Possible match, now check the string value - string attrName = ca.Name.ToString(); - if (attrName == EventSourceAttribute || attrName == EventSourceAttributeShort) - { - // Match add to candidates - _candidateClasses ??= new List(); - _candidateClasses.Add(classDeclaration); - return; - } - } - } - } - } + context.RegisterSourceOutput(eventSourceClasses, EmitSourceFile); } - private sealed class EventSourceClass - { - public string Namespace = string.Empty; - public string ClassName = string.Empty; - public string SourceName = string.Empty; - public Guid Guid = Guid.Empty; - } + private sealed record EventSourceClass(string Namespace, string ClassName, string SourceName, Guid Guid); } } diff --git a/src/libraries/System.Private.CoreLib/gen/System.Private.CoreLib.Generators.csproj b/src/libraries/System.Private.CoreLib/gen/System.Private.CoreLib.Generators.csproj index 6ce771f7124a87..b3274719f5a15a 100644 --- a/src/libraries/System.Private.CoreLib/gen/System.Private.CoreLib.Generators.csproj +++ b/src/libraries/System.Private.CoreLib/gen/System.Private.CoreLib.Generators.csproj @@ -11,8 +11,7 @@ - - +