| File: PseudoCustomAttributes.cs | Web Access |
| Project: ILAssembler.csproj (ILAssembler) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Reflection.Metadata.Ecma335; namespace ILAssembler; /// <summary> /// Lowers "pseudo custom attributes" into the metadata flag bits and auxiliary table rows that they /// represent, and suppresses the <c>CustomAttribute</c> row for the attributes that are not retained. /// </summary> /// <remarks> /// Recognizes the metadata transforms supported by native ILAsm, using C# compiler behavior for /// decoded argument values rather than legacy metadata-emitter validation. Attributes that only /// require validation are left untouched. Matching uses the namespace and name of the constructor's /// declaring type without requiring a particular assembly. /// </remarks> internal static partial class PseudoCustomAttributes { private static readonly Location s_unknownLocation = new(new SourceSpan(0, 0), new SourceText("", "")); public static void Lower(EntityRegistry registry, ImmutableArray<Diagnostic>.Builder diagnostics) { var attributes = new List<EntityRegistry.CustomAttributeEntity>(); foreach (var entity in registry.GetSeenEntities(TableIndex.CustomAttribute)) { if (entity is EntityRegistry.CustomAttributeEntity attribute) { attributes.Add(attribute); } } if (attributes.Count == 0) { return; } var lowered = new List<EntityRegistry.CustomAttributeEntity>(); foreach (var attribute in attributes) { if (attribute.Owner is null) { continue; } if (!TryGetAttributeTypeName(attribute.Constructor, out string @namespace, out string name)) { continue; } KnownAttribute? known = TryFindKnownAttribute(attribute.Constructor, @namespace, name); if (known is null) { continue; } var context = new LoweringContext(registry, diagnostics, attribute, @namespace, name); if (!Apply(context, known) || !known.KeepAttribute) { lowered.Add(attribute); } } registry.RemoveCustomAttributes(lowered); } private sealed class LoweringContext( EntityRegistry registry, ImmutableArray<Diagnostic>.Builder diagnostics, EntityRegistry.CustomAttributeEntity attribute, string @namespace, string name) { public EntityRegistry Registry { get; } = registry; public EntityRegistry.CustomAttributeEntity Attribute { get; } = attribute; /// <summary> /// The attribute target. References to members and types defined in this module are only /// resolved while metadata rows are written, which happens after this pass runs, so the /// owner recorded during parsing is resolved to the entity it designates here. /// </summary> public EntityRegistry.EntityBase Owner { get; } = ResolveOwner(registry, attribute.Owner!); public string AttributeName { get; } = @namespace.Length == 0 ? name : @namespace + "." + name; public bool Error(string id, string message) { diagnostics.Add(new Diagnostic(id, DiagnosticSeverity.Error, message, Attribute.Location ?? s_unknownLocation)); return false; } public bool InvalidTarget() => Error( DiagnosticIds.PseudoCustomAttributeInvalidTarget, string.Format(DiagnosticMessageTemplates.PseudoCustomAttributeInvalidTarget, AttributeName)); public bool InvalidValue() => Error( DiagnosticIds.PseudoCustomAttributeInvalidValue, string.Format(DiagnosticMessageTemplates.PseudoCustomAttributeInvalidValue, AttributeName)); public bool InvalidBlob() => Error( DiagnosticIds.PseudoCustomAttributeInvalidBlob, string.Format(DiagnosticMessageTemplates.PseudoCustomAttributeInvalidBlob, AttributeName)); public bool UnknownArgument(string argumentName) => Error( DiagnosticIds.PseudoCustomAttributeUnknownArgument, string.Format(DiagnosticMessageTemplates.PseudoCustomAttributeUnknownArgument, AttributeName, argumentName)); public bool RepeatedArgument(string argumentName) => Error( DiagnosticIds.PseudoCustomAttributeRepeatedArgument, string.Format(DiagnosticMessageTemplates.PseudoCustomAttributeRepeatedArgument, AttributeName, argumentName)); } }