/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/CSharp/Portable/Symbols/Symbol_Attributes.cs
978 строк
49 KB
Jan Jones
Merge branch 'main' into features/UnsafeEvolution
25 фев 2026, 12:38
Не верифицирован
25 фев 2026, 12:38
684f888
Код
Авторство
О чём код?
// 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. #nullable disable using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp { internal partial class Symbol { /// <summary> /// Gets the attributes for this symbol. Returns an empty <see cref="ImmutableArray<AttributeData>"/> if /// there are no attributes. /// </summary> public virtual ImmutableArray<CSharpAttributeData> GetAttributes() { Debug.Assert(!(this is IAttributeTargetSymbol)); //such types must override // Return an empty array by default. // Sub-classes that can have custom attributes must // override this method return ImmutableArray<CSharpAttributeData>.Empty; } /// <summary> /// Gets the attribute target kind corresponding to the symbol kind /// If attributes cannot be applied to this symbol kind, returns /// an invalid AttributeTargets value of 0 /// </summary> /// <returns>AttributeTargets or 0</returns> internal virtual AttributeTargets GetAttributeTarget() { switch (this.Kind) { case SymbolKind.Assembly: return AttributeTargets.Assembly; case SymbolKind.Field: return AttributeTargets.Field; case SymbolKind.Method: var method = (MethodSymbol)this; switch (method.MethodKind) { case MethodKind.Constructor: case MethodKind.StaticConstructor: return AttributeTargets.Constructor; default: return AttributeTargets.Method; } case SymbolKind.NamedType: var namedType = (NamedTypeSymbol)this; switch (namedType.TypeKind) { case TypeKind.Class: return AttributeTargets.Class; case TypeKind.Delegate: return AttributeTargets.Delegate; case TypeKind.Enum: return AttributeTargets.Enum; case TypeKind.Interface: return AttributeTargets.Interface; case TypeKind.Struct: return AttributeTargets.Struct; case TypeKind.TypeParameter: return AttributeTargets.GenericParameter; case TypeKind.Submission: // attributes can't be applied on a submission type: throw ExceptionUtilities.UnexpectedValue(namedType.TypeKind); } break; case SymbolKind.NetModule: return AttributeTargets.Module; case SymbolKind.Parameter: return AttributeTargets.Parameter; case SymbolKind.Property: return AttributeTargets.Property; case SymbolKind.Event: return AttributeTargets.Event; case SymbolKind.TypeParameter: return AttributeTargets.GenericParameter; } return 0; } /// <summary> /// Method to early decode the type of well-known attribute which can be queried during the BindAttributeType phase. /// This method is called first during attribute binding so that any attributes that affect semantics of type binding /// can be decoded here. /// </summary> /// <remarks> /// NOTE: If you are early decoding any new well-known attribute, make sure to update PostEarlyDecodeWellKnownAttributeTypes /// to default initialize this data. /// </remarks> internal virtual void EarlyDecodeWellKnownAttributeType(NamedTypeSymbol attributeType, AttributeSyntax attributeSyntax) { } /// <summary> /// This method is called during attribute binding after EarlyDecodeWellKnownAttributeTypes has been executed. /// Symbols should default initialize the data for early decoded well-known attributes here. /// </summary> internal virtual void PostEarlyDecodeWellKnownAttributeTypes() { } #nullable enable /// <summary> /// Method to early decode applied well-known attribute which can be queried by the binder. /// This method is called during attribute binding after we have bound the attribute types for all attributes, /// but haven't yet bound the attribute arguments/attribute constructor. /// Early decoding certain well-known attributes enables the binder to use this decoded information on this symbol /// when binding the attribute arguments/attribute constructor without causing attribute binding cycle. /// </summary> internal virtual (CSharpAttributeData?, BoundAttribute?) EarlyDecodeWellKnownAttribute(ref EarlyDecodeWellKnownAttributeArguments<EarlyWellKnownAttributeBinder, NamedTypeSymbol, AttributeSyntax, AttributeLocation> arguments) { return (null, null); } internal static bool EarlyDecodeDeprecatedOrExperimentalOrObsoleteAttribute( ref EarlyDecodeWellKnownAttributeArguments<EarlyWellKnownAttributeBinder, NamedTypeSymbol, AttributeSyntax, AttributeLocation> arguments, out CSharpAttributeData? attributeData, out BoundAttribute? boundAttribute, out ObsoleteAttributeData? obsoleteData) { var type = arguments.AttributeType; var syntax = arguments.AttributeSyntax; ObsoleteAttributeKind kind; if (CSharpAttributeData.IsTargetEarlyAttribute(type, syntax, AttributeDescription.ObsoleteAttribute)) { kind = ObsoleteAttributeKind.Obsolete; } else if (CSharpAttributeData.IsTargetEarlyAttribute(type, syntax, AttributeDescription.DeprecatedAttribute)) { kind = ObsoleteAttributeKind.Deprecated; } else if (CSharpAttributeData.IsTargetEarlyAttribute(type, syntax, AttributeDescription.WindowsExperimentalAttribute)) { kind = ObsoleteAttributeKind.WindowsExperimental; } else if (CSharpAttributeData.IsTargetEarlyAttribute(type, syntax, AttributeDescription.ExperimentalAttribute)) { kind = ObsoleteAttributeKind.Experimental; } else { obsoleteData = null; attributeData = null; boundAttribute = null; return false; } bool hasAnyDiagnostics; (attributeData, boundAttribute) = arguments.Binder.GetAttribute(syntax, type, beforeAttributePartBound: null, afterAttributePartBound: null, out hasAnyDiagnostics); if (!attributeData.HasErrors) { obsoleteData = attributeData.DecodeObsoleteAttribute(kind); if (hasAnyDiagnostics) { attributeData = null; boundAttribute = null; } } else { obsoleteData = null; attributeData = null; boundAttribute = null; } return true; } /// <summary> /// This method is called by the binder when it is finished binding a set of attributes on the symbol so that /// the symbol can extract data from the attribute arguments and potentially perform validation specific to /// some well known attributes. /// <para> /// NOTE: If we are decoding a well-known attribute that could be queried by the binder, consider decoding it during early decoding pass. /// </para> /// </summary> /// <remarks> /// <para> /// Symbol types should override this if they want to handle a specific well-known attribute. /// If the attribute is of a type that the symbol does not wish to handle, it should delegate back to /// this (base) method. /// </para> /// </remarks> protected void DecodeWellKnownAttribute(ref DecodeWellKnownAttributeArguments<AttributeSyntax, CSharpAttributeData, AttributeLocation> arguments) { Debug.Assert(arguments.Diagnostics.DiagnosticBag is not null); Debug.Assert(arguments.AttributeSyntaxOpt is not null); if (arguments.Attribute.IsTargetAttribute(AttributeDescription.CompilerFeatureRequiredAttribute)) { // Do not use '{FullName}'. This is reserved for compiler usage. arguments.Diagnostics.DiagnosticBag.Add(ErrorCode.ERR_ExplicitReservedAttr, arguments.AttributeSyntaxOpt.Location, AttributeDescription.CompilerFeatureRequiredAttribute.FullName); } else if (arguments.Attribute.IsTargetAttribute(AttributeDescription.ExperimentalAttribute)) { if (!SyntaxFacts.IsValidIdentifier((string?)arguments.Attribute.CommonConstructorArguments[0].ValueInternal)) { var attrArgumentLocation = arguments.Attribute.GetAttributeArgumentLocation(parameterIndex: 0); arguments.Diagnostics.DiagnosticBag.Add(ErrorCode.ERR_InvalidExperimentalDiagID, attrArgumentLocation); } } else if (arguments.Attribute.IsTargetAttribute(AttributeDescription.MetadataUpdateDeletedAttribute)) { arguments.Diagnostics.DiagnosticBag.Add(ErrorCode.ERR_AttributeCannotBeAppliedManually, arguments.AttributeSyntaxOpt!.Location, args: [AttributeDescription.MetadataUpdateDeletedAttribute.FullName]); } DecodeWellKnownAttributeImpl(ref arguments); } #nullable disable protected virtual void DecodeWellKnownAttributeImpl(ref DecodeWellKnownAttributeArguments<AttributeSyntax, CSharpAttributeData, AttributeLocation> arguments) { } /// <summary> /// Called to report attribute related diagnostics after all attributes have been bound and decoded. /// Called even if there are no attributes. /// </summary> /// <remarks> /// This method is called by the binder from <see cref="LoadAndValidateAttributes"/> after it has finished binding attributes on the symbol, /// has executed <see cref="DecodeWellKnownAttribute"/> for attributes applied on the symbol and has stored the decoded data in the /// lazyCustomAttributesBag on the symbol. Bound attributes haven't been stored on the bag yet. /// /// Post-validation for attributes that is dependent on other attributes can be done here. /// /// This method should not have any side effects on the symbol, i.e. it SHOULD NOT change the symbol state. /// </remarks> /// <param name="boundAttributes">Bound attributes.</param> /// <param name="allAttributeSyntaxNodes">Syntax nodes of attributes in order they are specified in source, or null if there are no attributes.</param> /// <param name="diagnostics">Diagnostic bag.</param> /// <param name="symbolPart">Specific part of the symbol to which the attributes apply, or <see cref="AttributeLocation.None"/> if the attributes apply to the symbol itself.</param> /// <param name="decodedData">Decoded well-known attribute data, could be null.</param> internal virtual void PostDecodeWellKnownAttributes(ImmutableArray<CSharpAttributeData> boundAttributes, ImmutableArray<AttributeSyntax> allAttributeSyntaxNodes, BindingDiagnosticBag diagnostics, AttributeLocation symbolPart, WellKnownAttributeData decodedData) { } #nullable enable /// <summary> /// This method does the following set of operations in the specified order: /// (1) GetAttributesToBind: Merge attributes from the given attributesSyntaxLists and filter out attributes by attribute target. /// (2) BindAttributeTypes: Bind all the attribute types to enable early decode of certain well-known attributes by type. /// (3) EarlyDecodeWellKnownAttributes: Perform early decoding of certain well-known attributes that could be queried by the binder in subsequent steps. /// (NOTE: This step has the side effect of updating the symbol state based on the data extracted from well known attributes). /// (4) GetAttributes: Bind the attributes (attribute arguments and constructor) using bound attribute types. /// (5) DecodeWellKnownAttributes: Decode and validate bound well known attributes. /// (NOTE: This step has the side effect of updating the symbol state based on the data extracted from well known attributes). /// (6) StoreBoundAttributesAndDoPostValidation: /// (a) Store the bound attributes in lazyCustomAttributes in a thread safe manner. /// (b) Perform some additional post attribute validations, such as /// 1) Duplicate attributes, attribute usage target validation, etc. /// 2) Post validation for attributes dependent on other attributes /// These validations cannot be performed prior to step 6(a) as we might need to /// perform a GetAttributes() call on a symbol which can introduce a cycle in attribute binding. /// We avoid this cycle by performing such validations in PostDecodeWellKnownAttributes after lazyCustomAttributes have been set. /// NOTE: PostDecodeWellKnownAttributes SHOULD NOT change the symbol state. /// </summary> /// <remarks> /// Current design of early decoding well-known attributes doesn't permit decoding attribute arguments/constructor as this can lead to binding cycles. /// For well-known attributes used by the binder, where we need the decoded arguments, we must handle them specially in one of the following possible ways: /// (a) Avoid decoding the attribute arguments during binding and delay the corresponding binder tasks to a separate post-pass executed after binding. /// (b) As the cycles can be caused only when we are binding attribute arguments/constructor, special case the corresponding binder tasks based on the current BinderFlags. /// </remarks> /// <param name="attributesSyntaxLists"></param> /// <param name="lazyCustomAttributesBag"></param> /// <param name="symbolPart">Specific part of the symbol to which the attributes apply, or <see cref="AttributeLocation.None"/> if the attributes apply to the symbol itself.</param> /// <param name="earlyDecodingOnly">Indicates that only early decoding should be performed. WARNING: the resulting bag will not be sealed.</param> /// <param name="binderOpt">Binder to use. If null, <see cref="DeclaringCompilation"/> GetBinderFactory will be used.</param> /// <param name="attributeMatchesOpt">If specified, only load attributes that match this predicate, and any diagnostics produced will be dropped.</param> /// <param name="beforeAttributePartBound">If specified, invoked before any part of the attribute syntax is bound.</param> /// <param name="afterAttributePartBound">If specified, invoked after any part of the attribute syntax is bound.</param> /// <returns>Flag indicating whether lazyCustomAttributes were stored on this thread. Caller should check for this flag and perform NotePartComplete if true.</returns> internal bool LoadAndValidateAttributes( OneOrMany<SyntaxList<AttributeListSyntax>> attributesSyntaxLists, ref CustomAttributesBag<CSharpAttributeData>? lazyCustomAttributesBag, AttributeLocation symbolPart = AttributeLocation.None, bool earlyDecodingOnly = false, Binder? binderOpt = null, Func<AttributeSyntax, Binder?, bool>? attributeMatchesOpt = null, Action<AttributeSyntax>? beforeAttributePartBound = null, Action<AttributeSyntax>? afterAttributePartBound = null) { var diagnostics = BindingDiagnosticBag.GetInstance(); var compilation = this.DeclaringCompilation; ImmutableArray<Binder> binders; BoundAttribute[]? boundAttributeArray; ImmutableArray<AttributeSyntax> attributesToBind = this.GetAttributesToBind(attributesSyntaxLists, symbolPart, diagnostics, compilation, attributeMatchesOpt, binderOpt, out binders); int totalAttributesCount = attributesToBind.Length; Debug.Assert(!attributesToBind.IsDefault); ImmutableArray<CSharpAttributeData> boundAttributes; WellKnownAttributeData? wellKnownAttributeData; if (totalAttributesCount != 0) { Debug.Assert(!binders.IsDefault); Debug.Assert(binders.Length == attributesToBind.Length); // Initialize the bag so that data decoded from early attributes can be stored onto it. if (lazyCustomAttributesBag == null) { Interlocked.CompareExchange(ref lazyCustomAttributesBag, new CustomAttributesBag<CSharpAttributeData>(), null); } // Bind the attribute types and then early decode them. var attributeTypesBuilder = new NamedTypeSymbol[totalAttributesCount]; Binder.BindAttributeTypes(binders, attributesToBind, this, attributeTypesBuilder, beforeAttributePartBound, afterAttributePartBound, diagnostics); bool interestedInDiagnostics = !earlyDecodingOnly && attributeMatchesOpt is null; if (interestedInDiagnostics) { for (var i = 0; i < totalAttributesCount; i++) { if (attributeTypesBuilder[i].IsGenericType) { MessageID.IDS_FeatureGenericAttributes.CheckFeatureAvailability(diagnostics, attributesToBind[i]); } } } ImmutableArray<NamedTypeSymbol> boundAttributeTypes = attributeTypesBuilder.AsImmutableOrNull(); this.EarlyDecodeWellKnownAttributeTypes(boundAttributeTypes, attributesToBind); this.PostEarlyDecodeWellKnownAttributeTypes(); // Bind the attribute in two stages - early and normal. var attributeDataArray = new CSharpAttributeData[totalAttributesCount]; boundAttributeArray = interestedInDiagnostics ? new BoundAttribute[totalAttributesCount] : null; // Early bind and decode some well-known attributes. EarlyWellKnownAttributeData? earlyData = this.EarlyDecodeWellKnownAttributes(binders, boundAttributeTypes, attributesToBind, symbolPart, attributeDataArray, boundAttributeArray); Debug.Assert(!attributeDataArray.Contains((attr) => attr != null && attr.HasErrors)); // Store data decoded from early bound well-known attributes. // TODO: what if this succeeds on another thread, not ours? lazyCustomAttributesBag.SetEarlyDecodedWellKnownAttributeData(earlyData); if (earlyDecodingOnly) { diagnostics.Free(); //NOTE: dropped. return false; } // Bind attributes. Binder.GetAttributes(binders, attributesToBind, boundAttributeTypes, attributeDataArray, boundAttributeArray, beforeAttributePartBound, afterAttributePartBound, diagnostics); boundAttributes = attributeDataArray.AsImmutableOrNull(); // All attributes must be bound by now. Debug.Assert(!boundAttributes.Any(static (attr) => attr == null)); // Validate attribute usage and Decode remaining well-known attributes. wellKnownAttributeData = this.ValidateAttributeUsageAndDecodeWellKnownAttributes(binders, attributesToBind, boundAttributes, diagnostics, symbolPart); // Store data decoded from remaining well-known attributes. // TODO: what if this succeeds on another thread but not this thread? lazyCustomAttributesBag.SetDecodedWellKnownAttributeData(wellKnownAttributeData); } else if (earlyDecodingOnly) { diagnostics.Free(); //NOTE: dropped. return false; } else { boundAttributes = ImmutableArray<CSharpAttributeData>.Empty; boundAttributeArray = null; wellKnownAttributeData = null; Interlocked.CompareExchange(ref lazyCustomAttributesBag, CustomAttributesBag<CSharpAttributeData>.WithEmptyData(), null); this.PostEarlyDecodeWellKnownAttributeTypes(); } Debug.Assert(!earlyDecodingOnly); // Store attributes into the bag. bool lazyAttributesStoredOnThisThread = false; if (lazyCustomAttributesBag.SetAttributes(boundAttributes)) { if (attributeMatchesOpt is null) { this.PostDecodeWellKnownAttributes(boundAttributes, attributesToBind, diagnostics, symbolPart, wellKnownAttributeData); removeObsoleteDiagnosticsForForwardedTypes(boundAttributes, attributesToBind, ref diagnostics); Debug.Assert(diagnostics.DiagnosticBag is not null); this.RecordPresenceOfBadAttributes(boundAttributes); if (totalAttributesCount != 0) { Debug.Assert(boundAttributeArray is not null); for (var i = 0; i < totalAttributesCount; i++) { var boundAttribute = boundAttributeArray[i]; Debug.Assert(boundAttribute is not null); Binder attributeBinder = binders[i]; if (boundAttribute.Constructor is { } ctor) { Binder.CheckRequiredMembersInObjectInitializer(ctor, ImmutableArray<BoundExpression>.CastUp(boundAttribute.NamedArguments), boundAttribute.Syntax, diagnostics); attributeBinder.ReportDiagnosticsIfObsolete(diagnostics, ctor, boundAttribute.Syntax, hasBaseReceiver: false); attributeBinder.ReportDiagnosticsIfUnsafeMemberAccess(diagnostics, ctor, boundAttribute.Syntax); } NullableWalker.AnalyzeIfNeeded(attributeBinder, boundAttribute, boundAttribute.Syntax, diagnostics.DiagnosticBag); } } AddDeclarationDiagnostics(diagnostics); } lazyAttributesStoredOnThisThread = true; if (lazyCustomAttributesBag.IsEmpty) lazyCustomAttributesBag = CustomAttributesBag<CSharpAttributeData>.Empty; } Debug.Assert(lazyCustomAttributesBag.IsSealed); diagnostics.Free(); return lazyAttributesStoredOnThisThread; void removeObsoleteDiagnosticsForForwardedTypes(ImmutableArray<CSharpAttributeData> boundAttributes, ImmutableArray<AttributeSyntax> attributesToBind, ref BindingDiagnosticBag diagnostics) { Debug.Assert(diagnostics.DiagnosticBag is not null); if (!boundAttributes.IsDefaultOrEmpty && this is SourceAssemblySymbol && !diagnostics.DiagnosticBag.IsEmptyWithoutResolution && diagnostics.DiagnosticBag.AsEnumerableWithoutResolution().OfType<DiagnosticWithInfo>().Where(isObsoleteDiagnostic).Any()) { // We are binding attributes for an assembly and have an obsolete diagnostic reported, // or we have lazy diagnostic, that might be resolved to an obsolete diagnostic later. // We would like to filter out a diagnostic like that for a forwarded type. // The TypeForwardedTo attribute takes only one argument, which must be System.Type and it // designates the forwarded type. The only form of System.Type value accepted // as an argument for an attribute is a 'typeof' expression. The only obsolete diagnostics // that can be reported for a 'typeof' expression, is diagnostics for its argument, which is // the reference to a type. A forwarded type, when we are dealing with a TypeForwardedTo // application. // The general strategy: // 1. Collect locations of the first argument of each TypeForwardedTo attribute application. // 2. Collect obsolete diagnostics reported within the span of those locations. // 3. Remove the collected diagnostics, if any. var builder = ArrayBuilder<Location>.GetInstance(); int totalAttributesCount = attributesToBind.Length; Debug.Assert(totalAttributesCount == boundAttributes.Length); // 1. Collect locations of the first argument of each TypeForwardedTo attribute application. for (int i = 0; i < totalAttributesCount; i++) { CSharpAttributeData boundAttribute = boundAttributes[i]; if (!boundAttribute.HasErrors && boundAttribute.IsTargetAttribute(AttributeDescription.TypeForwardedToAttribute) && boundAttribute.CommonConstructorArguments[0].ValueInternal is TypeSymbol && attributesToBind[i].ArgumentList?.Arguments[0].Expression.Location is { } location) { builder.Add(location); } } if (builder.Count != 0) { var toRemove = new HashSet<Diagnostic>(ReferenceEqualityComparer.Instance); // 2. Collect obsolete diagnostics reported within the span of those locations. foreach (Diagnostic d in diagnostics.DiagnosticBag.AsEnumerableWithoutResolution()) { if (d is DiagnosticWithInfo withInfo && isObsoleteDiagnostic(withInfo)) { Location location = withInfo.Location; foreach (Location argumentLocation in builder) { if (location.SourceTree == argumentLocation.SourceTree && argumentLocation.SourceSpan.Contains(location.SourceSpan)) { toRemove.Add(withInfo); break; } } } } // 3. Remove the collected diagnostics, if any. if (toRemove.Count != 0) { var filtered = BindingDiagnosticBag.GetInstance(); filtered.AddDependencies(diagnostics); foreach (Diagnostic d in diagnostics.DiagnosticBag.AsEnumerableWithoutResolution()) { if (!toRemove.Contains(d)) { filtered.Add(d); } } diagnostics.Free(); diagnostics = filtered; } } builder.Free(); } } static bool isObsoleteDiagnostic(DiagnosticWithInfo d) { return d.HasLazyInfo ? d.LazyInfo is LazyObsoleteDiagnosticInfo : d.Info.IsObsoleteDiagnostic(); } } /// <summary> /// Binds attributes applied to this symbol. /// </summary> protected ImmutableArray<(CSharpAttributeData, BoundAttribute)> BindAttributes(OneOrMany<SyntaxList<AttributeListSyntax>> attributeDeclarations, Binder? rootBinder) { var boundAttributeArrayBuilder = ArrayBuilder<(CSharpAttributeData, BoundAttribute)>.GetInstance(); foreach (var attributeListSyntaxList in attributeDeclarations) { var binder = GetAttributeBinder(attributeListSyntaxList, DeclaringCompilation, rootBinder); foreach (var attributeListSyntax in attributeListSyntaxList) { foreach (var attributeSyntax in attributeListSyntax.Attributes) { var boundType = binder.BindType(attributeSyntax.Name, BindingDiagnosticBag.Discarded); var boundTypeSymbol = (NamedTypeSymbol)boundType.Type; var boundAttribute = binder.GetAttribute(attributeSyntax, boundTypeSymbol, beforeAttributePartBound: null, afterAttributePartBound: null, BindingDiagnosticBag.Discarded); boundAttributeArrayBuilder.Add(boundAttribute); } } } return boundAttributeArrayBuilder.ToImmutableAndFree(); } #nullable disable private void RecordPresenceOfBadAttributes(ImmutableArray<CSharpAttributeData> boundAttributes) { foreach (var attribute in boundAttributes) { if (attribute.HasErrors) { CSharpCompilation compilation = this.DeclaringCompilation; Debug.Assert(compilation != null); ((SourceModuleSymbol)compilation.SourceModule).RecordPresenceOfBadAttributes(); break; } } } /// <summary> /// Method to merge attributes from the given attributesSyntaxLists and filter out attributes by attribute target. /// This is the first step in attribute binding. /// </summary> /// <remarks> /// This method can generate diagnostics for few cases where we have an invalid target specifier and the parser hasn't generated the necessary diagnostics. /// It should not perform any bind operations as it can lead to an attribute binding cycle. /// </remarks> private ImmutableArray<AttributeSyntax> GetAttributesToBind( OneOrMany<SyntaxList<AttributeListSyntax>> attributeDeclarationSyntaxLists, AttributeLocation symbolPart, BindingDiagnosticBag diagnostics, CSharpCompilation compilation, Func<AttributeSyntax, Binder, bool> attributeMatchesOpt, Binder rootBinderOpt, out ImmutableArray<Binder> binders) { var attributeTarget = (IAttributeTargetSymbol)this; ArrayBuilder<AttributeSyntax> syntaxBuilder = null; ArrayBuilder<Binder> bindersBuilder = null; int attributesToBindCount = 0; for (int listIndex = 0; listIndex < attributeDeclarationSyntaxLists.Count; listIndex++) { var attributeDeclarationSyntaxList = attributeDeclarationSyntaxLists[listIndex]; if (attributeDeclarationSyntaxList.Any()) { int prevCount = attributesToBindCount; foreach (var attributeDeclarationSyntax in attributeDeclarationSyntaxList) { // We bind the attribute only if it has a matching target for the given ownerSymbol and attributeLocation. if (MatchAttributeTarget(attributeTarget, symbolPart, attributeDeclarationSyntax, diagnostics) && ShouldBindAttributes(attributeDeclarationSyntax, diagnostics)) { if (syntaxBuilder == null) { syntaxBuilder = new ArrayBuilder<AttributeSyntax>(); bindersBuilder = new ArrayBuilder<Binder>(); } var attributesToBind = attributeDeclarationSyntax.Attributes; if (attributeMatchesOpt is null) { syntaxBuilder.AddRange(attributesToBind); attributesToBindCount += attributesToBind.Count; } else { foreach (var attribute in attributesToBind) { if (attributeMatchesOpt(attribute, rootBinderOpt)) { syntaxBuilder.Add(attribute); attributesToBindCount++; } } } } } if (attributesToBindCount != prevCount) { Debug.Assert(bindersBuilder != null); var binder = GetAttributeBinder(attributeDeclarationSyntaxList, compilation, rootBinderOpt); for (int i = 0; i < attributesToBindCount - prevCount; i++) { bindersBuilder.Add(binder); } } } } if (syntaxBuilder != null) { binders = bindersBuilder.ToImmutableAndFree(); return syntaxBuilder.ToImmutableAndFree(); } else { binders = ImmutableArray<Binder>.Empty; return ImmutableArray<AttributeSyntax>.Empty; } } protected virtual bool ShouldBindAttributes(AttributeListSyntax attributeDeclarationSyntax, BindingDiagnosticBag diagnostics) { return true; } #nullable enable protected Binder GetAttributeBinder(SyntaxList<AttributeListSyntax> attributeDeclarationSyntaxList, CSharpCompilation compilation, Binder? rootBinder = null) { var binder = rootBinder ?? compilation.GetBinderFactory(attributeDeclarationSyntaxList.Node!.SyntaxTree).GetBinder(attributeDeclarationSyntaxList.Node); binder = new ContextualAttributeBinder(binder, this); Debug.Assert(!binder.InAttributeArgument || this is MethodSymbol { MethodKind: MethodKind.LambdaMethod or MethodKind.LocalFunction }, "Possible cycle in attribute binding"); return binder; } #nullable disable private static bool MatchAttributeTarget(IAttributeTargetSymbol attributeTarget, AttributeLocation symbolPart, AttributeListSyntax attributeList, BindingDiagnosticBag diagnostics) { AttributeLocation defaultAttributeLocation = attributeTarget.DefaultAttributeLocation; if (defaultAttributeLocation == AttributeLocation.Extension) { diagnostics.Add(ErrorCode.ERR_AttributesNotAllowed, attributeList); return false; } IAttributeTargetSymbol attributesOwner = attributeTarget.AttributesOwner; // Determine if the target symbol owns the attribute declaration. // We need to report diagnostics only once, so do it when visiting attributes for the owner. bool isOwner = symbolPart == AttributeLocation.None && ReferenceEquals(attributesOwner, attributeTarget); AttributeTargetSpecifierSyntax targetOpt = attributeList.Target; if (targetOpt == null) { // only attributes with an explicit target match if the symbol doesn't own the attributes: return isOwner; } // Special error code for this case. if (isOwner && targetOpt.Identifier.ToAttributeLocation() == AttributeLocation.Module) { var parseOptions = (CSharpParseOptions)targetOpt.SyntaxTree.Options; if (parseOptions.LanguageVersion == LanguageVersion.CSharp1) diagnostics.Add(ErrorCode.WRN_NonECMAFeature, targetOpt.GetLocation(), MessageID.IDS_FeatureModuleAttrLoc); } AttributeLocation allowedTargets = attributesOwner.AllowedAttributeLocations; AttributeLocation explicitTarget = targetOpt.GetAttributeLocation(); if (explicitTarget == AttributeLocation.None) { // error: unknown attribute location if (isOwner) { //NOTE: ValueText so that we accept targets like "@return", to match dev10 (DevDiv #2591). diagnostics.Add(ErrorCode.WRN_InvalidAttributeLocation, targetOpt.Identifier.GetLocation(), targetOpt.Identifier.ValueText, allowedTargets.ToDisplayString()); } return false; } if ((explicitTarget & allowedTargets) == 0) { // error: invalid target for symbol if (isOwner) { if (allowedTargets == AttributeLocation.None) { switch (defaultAttributeLocation) { case AttributeLocation.Assembly: case AttributeLocation.Module: // global attributes are disallowed in interactive code: diagnostics.Add(ErrorCode.ERR_GlobalAttributesNotAllowed, targetOpt.Identifier.GetLocation()); break; default: // currently this can't happen throw ExceptionUtilities.UnexpectedValue(attributeTarget.DefaultAttributeLocation); } } else { diagnostics.Add(ErrorCode.WRN_AttributeLocationOnBadDeclaration, targetOpt.Identifier.GetLocation(), targetOpt.Identifier.ToString(), allowedTargets.ToDisplayString()); } } return false; } if (symbolPart == AttributeLocation.None) { return explicitTarget == attributeTarget.DefaultAttributeLocation; } else { return explicitTarget == symbolPart; } } #nullable enable /// <summary> /// Method to early decode certain well-known attributes which can be queried by the binder. /// This method is called during attribute binding after we have bound the attribute types for all attributes, /// but haven't yet bound the attribute arguments/attribute constructor. /// Early decoding certain well-known attributes enables the binder to use this decoded information on this symbol /// when binding the attribute arguments/attribute constructor without causing attribute binding cycle. /// </summary> internal EarlyWellKnownAttributeData? EarlyDecodeWellKnownAttributes( ImmutableArray<Binder> binders, ImmutableArray<NamedTypeSymbol> boundAttributeTypes, ImmutableArray<AttributeSyntax> attributesToBind, AttributeLocation symbolPart, CSharpAttributeData?[] attributeDataArray, BoundAttribute?[]? boundAttributeArray) { Debug.Assert(boundAttributeTypes.Any()); Debug.Assert(attributesToBind.Any()); Debug.Assert(binders.Any()); Debug.Assert(attributeDataArray != null); Debug.Assert(!attributeDataArray.Contains((attr) => attr != null)); var earlyBinder = new EarlyWellKnownAttributeBinder(binders[0]); var arguments = new EarlyDecodeWellKnownAttributeArguments<EarlyWellKnownAttributeBinder, NamedTypeSymbol, AttributeSyntax, AttributeLocation>(); arguments.SymbolPart = symbolPart; for (int i = 0; i < boundAttributeTypes.Length; i++) { NamedTypeSymbol boundAttributeType = boundAttributeTypes[i]; if (!boundAttributeType.IsErrorType()) { if (binders[i] != earlyBinder.Next) { earlyBinder = new EarlyWellKnownAttributeBinder(binders[i]); } arguments.Binder = earlyBinder; arguments.AttributeType = boundAttributeType; arguments.AttributeSyntax = attributesToBind[i]; // Early bind some well-known attributes (CSharpAttributeData? earlyAttributeDataOpt, BoundAttribute? boundAttributeOpt) = this.EarlyDecodeWellKnownAttribute(ref arguments); Debug.Assert(earlyAttributeDataOpt == null || !earlyAttributeDataOpt.HasErrors); Debug.Assert(boundAttributeOpt is null == earlyAttributeDataOpt is null); attributeDataArray[i] = earlyAttributeDataOpt; if (boundAttributeArray is not null) { boundAttributeArray[i] = boundAttributeOpt; } } } return arguments.HasDecodedData ? arguments.DecodedData : null; } #nullable disable private void EarlyDecodeWellKnownAttributeTypes(ImmutableArray<NamedTypeSymbol> attributeTypes, ImmutableArray<AttributeSyntax> attributeSyntaxList) { Debug.Assert(attributeSyntaxList.Any()); Debug.Assert(attributeTypes.Any()); for (int i = 0; i < attributeTypes.Length; i++) { var attributeType = attributeTypes[i]; if (!attributeType.IsErrorType()) { this.EarlyDecodeWellKnownAttributeType(attributeType, attributeSyntaxList[i]); } } } /// <summary> /// This method validates attribute usage for each bound attribute and calls <see cref="DecodeWellKnownAttributeImpl"/> /// on attributes with valid attribute usage. /// This method is called by the binder when it is finished binding a set of attributes on the symbol so that /// the symbol can extract data from the attribute arguments and potentially perform validation specific to /// some well known attributes. /// </summary> private WellKnownAttributeData ValidateAttributeUsageAndDecodeWellKnownAttributes( ImmutableArray<Binder> binders, ImmutableArray<AttributeSyntax> attributeSyntaxList, ImmutableArray<CSharpAttributeData> boundAttributes, BindingDiagnosticBag diagnostics, AttributeLocation symbolPart) { Debug.Assert(binders.Any()); Debug.Assert(attributeSyntaxList.Any()); Debug.Assert(boundAttributes.Any()); Debug.Assert(binders.Length == boundAttributes.Length); Debug.Assert(attributeSyntaxList.Length == boundAttributes.Length); int totalAttributesCount = boundAttributes.Length; HashSet<NamedTypeSymbol> uniqueAttributeTypes = new HashSet<NamedTypeSymbol>(); var arguments = new DecodeWellKnownAttributeArguments<AttributeSyntax, CSharpAttributeData, AttributeLocation>(); arguments.Diagnostics = diagnostics; arguments.AttributesCount = totalAttributesCount; arguments.SymbolPart = symbolPart; for (int i = 0; i < totalAttributesCount; i++) { CSharpAttributeData boundAttribute = boundAttributes[i]; AttributeSyntax attributeSyntax = attributeSyntaxList[i]; Binder binder = binders[i]; // Decode attribute as a possible well-known attribute only if it has no binding errors and has valid AttributeUsage. if (!boundAttribute.HasErrors && ValidateAttributeUsage(boundAttribute, attributeSyntax, binder.Compilation, symbolPart, diagnostics, uniqueAttributeTypes)) { arguments.Attribute = boundAttribute; arguments.AttributeSyntaxOpt = attributeSyntax; arguments.Index = i; this.DecodeWellKnownAttribute(ref arguments); } } return arguments.HasDecodedData ? arguments.DecodedData : null; } /// <summary> /// Validate attribute usage target and duplicate attributes. /// </summary> /// <param name="attribute">Bound attribute</param> /// <param name="node">Syntax node for attribute specification</param> /// <param name="compilation">Compilation</param> /// <param name="symbolPart">Symbol part to which the attribute has been applied.</param> /// <param name="diagnostics">Diagnostics</param> /// <param name="uniqueAttributeTypes">Set of unique attribute types applied to the symbol</param> private bool ValidateAttributeUsage( CSharpAttributeData attribute, AttributeSyntax node, CSharpCompilation compilation, AttributeLocation symbolPart, BindingDiagnosticBag diagnostics, HashSet<NamedTypeSymbol> uniqueAttributeTypes) { Debug.Assert(!attribute.HasErrors); NamedTypeSymbol attributeType = attribute.AttributeClass; AttributeUsageInfo attributeUsageInfo = attributeType.GetAttributeUsageInfo(); if (this is NamedTypeSymbol { IsExtension: true }) { diagnostics.Add(ErrorCode.ERR_AttributesNotAllowed, node.Name.Location); return false; } // Given attribute can't be specified more than once if AllowMultiple is false. if (!uniqueAttributeTypes.Add(attributeType.OriginalDefinition) && !attributeUsageInfo.AllowMultiple) { diagnostics.Add(ErrorCode.ERR_DuplicateAttribute, node.Name.Location, node.GetErrorDisplayName()); return false; } // Verify if the attribute type can be applied to given owner symbol. AttributeTargets attributeTarget; if (symbolPart == AttributeLocation.Return) { // attribute on return type Debug.Assert(this.Kind == SymbolKind.Method); attributeTarget = AttributeTargets.ReturnValue; } else { attributeTarget = this.GetAttributeTarget(); } if ((attributeTarget & attributeUsageInfo.ValidTargets) == 0) { // generate error diagnostics.Add(ErrorCode.ERR_AttributeOnBadSymbolType, node.Name.Location, node.GetErrorDisplayName(), attributeUsageInfo.GetValidTargetsErrorArgument()); return false; } if (attribute.IsSecurityAttribute(compilation)) { switch (this.Kind) { case SymbolKind.Assembly: case SymbolKind.NamedType: case SymbolKind.Method: break; default: // CS7070: Security attribute '{0}' is not valid on this declaration type. Security attributes are only valid on assembly, type and method declarations. diagnostics.Add(ErrorCode.ERR_SecurityAttributeInvalidTarget, node.Name.Location, node.GetErrorDisplayName()); return false; } } return true; } /// <summary> /// Ensure that attributes are bound and the ObsoleteState/ExperimentalState of this symbol is known. /// </summary> internal void ForceCompleteObsoleteAttribute() { if (this.ObsoleteKind == ObsoleteAttributeKind.Uninitialized) { this.GetAttributes(); } Debug.Assert(this.ObsoleteState != ThreeState.Unknown, "ObsoleteState should be true or false now."); Debug.Assert(this.ExperimentalState != ThreeState.Unknown, "ExperimentalState should be true or false now."); this.ContainingSymbol?.ForceCompleteObsoleteAttribute(); } } }