/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs
1 782 строки
84 KB
Jan Jones
Unsafe evolution: consolidate reserved attributes (#83908)
03 июн 2026, 16:10
Не верифицирован
03 июн 2026, 16:10
d74775a
Код
Авторство
О чём код?
// 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.Runtime.InteropServices; using System.Text; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Symbols { /// <summary> /// A source method that can have attributes, including a member method, accessor, or local function. /// </summary> internal abstract partial class SourceMethodSymbol : IAttributeTargetSymbol { private CustomAttributesBag<CSharpAttributeData> _lazyCustomAttributesBag; private CustomAttributesBag<CSharpAttributeData> _lazyReturnTypeCustomAttributesBag; // some symbols may not have a syntax (e.g. lambdas, synthesized event accessors) protected readonly SyntaxReference syntaxReferenceOpt; protected SourceMethodSymbol(SyntaxReference syntaxReferenceOpt) { this.syntaxReferenceOpt = syntaxReferenceOpt; } #nullable enable /// <summary> /// Gets the syntax node used for the in-method binder. /// </summary> protected CSharpSyntaxNode? GetInMethodSyntaxNode() { switch (SyntaxNode) { case ConstructorDeclarationSyntax constructor: return constructor.Initializer ?? (CSharpSyntaxNode?)constructor.Body ?? constructor.ExpressionBody; case BaseMethodDeclarationSyntax method: return (CSharpSyntaxNode?)method.Body ?? method.ExpressionBody; case AccessorDeclarationSyntax accessor: return (CSharpSyntaxNode?)accessor.Body ?? accessor.ExpressionBody; case ArrowExpressionClauseSyntax arrowExpression: Debug.Assert(arrowExpression.Parent!.Kind() == SyntaxKind.PropertyDeclaration || arrowExpression.Parent.Kind() == SyntaxKind.IndexerDeclaration); return arrowExpression; case LocalFunctionStatementSyntax localFunction: return (CSharpSyntaxNode?)localFunction.Body ?? localFunction.ExpressionBody; case CompilationUnitSyntax _ when this is SynthesizedSimpleProgramEntryPointSymbol entryPoint: return (CSharpSyntaxNode)entryPoint.ReturnTypeSyntax; case RecordDeclarationSyntax recordDecl: Debug.Assert(recordDecl.IsKind(SyntaxKind.RecordDeclaration)); return recordDecl; case ClassDeclarationSyntax classDecl: return classDecl; default: return null; } } internal virtual Binder? OuterBinder => null; internal virtual Binder? WithTypeParametersBinder => null; #nullable disable internal SyntaxReference SyntaxRef { get { return this.syntaxReferenceOpt; } } internal CSharpSyntaxNode SyntaxNode { get { return (this.syntaxReferenceOpt == null) ? null : (CSharpSyntaxNode)this.syntaxReferenceOpt.GetSyntax(); } } internal SyntaxTree SyntaxTree { get { return this.syntaxReferenceOpt == null ? null : this.syntaxReferenceOpt.SyntaxTree; } } public override ImmutableArray<SyntaxReference> DeclaringSyntaxReferences { get { return (this.syntaxReferenceOpt == null) ? ImmutableArray<SyntaxReference>.Empty : ImmutableArray.Create(this.syntaxReferenceOpt); } } public override FlowAnalysisAnnotations ReturnTypeFlowAnalysisAnnotations => DecodeReturnTypeAnnotationAttributes(GetDecodedReturnTypeWellKnownAttributeData()); public override ImmutableHashSet<string> ReturnNotNullIfParameterNotNull => GetDecodedReturnTypeWellKnownAttributeData()?.NotNullIfParameterNotNull ?? ImmutableHashSet<string>.Empty; /// <summary> /// Symbol to copy bound attributes from, or null if the attributes are not shared among multiple source method symbols. /// </summary> /// <remarks> /// Used for example for event accessors. The "remove" method delegates attribute binding to the "add" method. /// The bound attribute data are then applied to both accessors. /// </remarks> protected virtual SourceMemberMethodSymbol BoundAttributesSource { get { return null; } } protected virtual IAttributeTargetSymbol AttributeOwner { get { return this; } } protected virtual AttributeLocation AttributeLocationForLoadAndValidateAttributes { get { return AttributeLocation.None; } } IAttributeTargetSymbol IAttributeTargetSymbol.AttributesOwner { get { return this.AttributeOwner; } } AttributeLocation IAttributeTargetSymbol.DefaultAttributeLocation { get { return AttributeLocation.Method; } } AttributeLocation IAttributeTargetSymbol.AllowedAttributeLocations { get { switch (MethodKind) { case MethodKind.Constructor: case MethodKind.Destructor: case MethodKind.StaticConstructor: return AttributeLocation.Method; case MethodKind.PropertySet: case MethodKind.EventRemove: case MethodKind.EventAdd: return AttributeLocation.Method | AttributeLocation.Return | AttributeLocation.Parameter; default: return AttributeLocation.Method | AttributeLocation.Return; } } } /// <summary> /// Gets the syntax list of custom attributes that declares attributes for this method symbol. /// </summary> internal virtual OneOrMany<SyntaxList<AttributeListSyntax>> GetAttributeDeclarations() { return OneOrMany.Create(default(SyntaxList<AttributeListSyntax>)); } /// <summary> /// Gets the syntax list of custom attributes that declares attributes for return type of this method. /// </summary> internal virtual OneOrMany<SyntaxList<AttributeListSyntax>> GetReturnTypeAttributeDeclarations() { // Usually the same list as other attributes applied on the method, but e.g. // constructors and destructors do not allow return-type attributes, so this is empty. return GetAttributeDeclarations(); } /// <summary> /// Returns data decoded from special early bound well-known attributes applied to the symbol or null if there are no applied attributes. /// </summary> /// <remarks> /// Forces binding and decoding of attributes. /// </remarks> internal MethodEarlyWellKnownAttributeData GetEarlyDecodedWellKnownAttributeData() { var attributesBag = _lazyCustomAttributesBag; if (attributesBag == null || !attributesBag.IsEarlyDecodedWellKnownAttributeDataComputed) { attributesBag = this.GetAttributesBag(); } return (MethodEarlyWellKnownAttributeData)attributesBag.EarlyDecodedWellKnownAttributeData; } /// <summary> /// Returns data decoded from well-known attributes applied to the symbol or null if there are no applied attributes. /// </summary> /// <remarks> /// Forces binding and decoding of attributes. /// </remarks> protected MethodWellKnownAttributeData GetDecodedWellKnownAttributeData() { var attributesBag = _lazyCustomAttributesBag; if (attributesBag == null || !attributesBag.IsDecodedWellKnownAttributeDataComputed) { attributesBag = this.GetAttributesBag(); } return (MethodWellKnownAttributeData)attributesBag.DecodedWellKnownAttributeData; } /// <summary> /// Returns information retrieved from custom attributes on return type in source, or null if the symbol is not source symbol or there are none. /// </summary> /// <remarks> /// Forces binding and decoding of attributes. /// </remarks> internal ReturnTypeWellKnownAttributeData GetDecodedReturnTypeWellKnownAttributeData() { var attributesBag = _lazyReturnTypeCustomAttributesBag; if (attributesBag == null || !attributesBag.IsDecodedWellKnownAttributeDataComputed) { attributesBag = this.GetReturnTypeAttributesBag(); } return (ReturnTypeWellKnownAttributeData)attributesBag.DecodedWellKnownAttributeData; } /// <summary> /// Returns a bag of applied custom attributes and data decoded from well-known attributes. Returns null if there are no attributes applied on the symbol. /// </summary> /// <remarks> /// Forces binding and decoding of attributes. /// </remarks> private CustomAttributesBag<CSharpAttributeData> GetAttributesBag() { var bag = _lazyCustomAttributesBag; if (bag != null && bag.IsSealed) { return bag; } return GetAttributesBag(ref _lazyCustomAttributesBag, forReturnType: false); } /// <summary> /// Returns a bag of custom attributes applied on the method return value and data decoded from well-known attributes. Returns null if there are no attributes. /// </summary> /// <remarks> /// Forces binding and decoding of attributes. /// </remarks> private CustomAttributesBag<CSharpAttributeData> GetReturnTypeAttributesBag() { var bag = _lazyReturnTypeCustomAttributesBag; if (bag != null && bag.IsSealed) { return bag; } return GetAttributesBag(ref _lazyReturnTypeCustomAttributesBag, forReturnType: true); } private CustomAttributesBag<CSharpAttributeData> GetAttributesBag(ref CustomAttributesBag<CSharpAttributeData> lazyCustomAttributesBag, bool forReturnType) { var copyFrom = this.BoundAttributesSource; // prevent infinite recursion: Debug.Assert(!ReferenceEquals(copyFrom, this)); bool bagCreatedOnThisThread; if ((object)copyFrom != null) { var attributesBag = forReturnType ? copyFrom.GetReturnTypeAttributesBag() : copyFrom.GetAttributesBag(); bagCreatedOnThisThread = Interlocked.CompareExchange(ref lazyCustomAttributesBag, attributesBag, null) == null; } else { var (declarations, symbolPart) = forReturnType ? (GetReturnTypeAttributeDeclarations(), AttributeLocation.Return) : (GetAttributeDeclarations(), AttributeLocationForLoadAndValidateAttributes); bagCreatedOnThisThread = LoadAndValidateAttributes( declarations, ref lazyCustomAttributesBag, symbolPart, binderOpt: OuterBinder); } if (bagCreatedOnThisThread) { NoteAttributesComplete(forReturnType); } return lazyCustomAttributesBag; } /// <summary> /// Called when this thread loaded the method's attributes. For method symbols with completion state. /// </summary> protected abstract void NoteAttributesComplete(bool forReturnType); /// <summary> /// Gets the attributes applied on this symbol. /// Returns an empty array if there are no attributes. /// </summary> public override ImmutableArray<CSharpAttributeData> GetAttributes() { return this.GetAttributesBag().Attributes; } /// <summary> /// Gets the attributes applied on the return value of this method symbol. /// Returns an empty array if there are no attributes. /// </summary> public override ImmutableArray<CSharpAttributeData> GetReturnTypeAttributes() { return this.GetReturnTypeAttributesBag().Attributes; } #nullable enable internal override (CSharpAttributeData?, BoundAttribute?) EarlyDecodeWellKnownAttribute(ref EarlyDecodeWellKnownAttributeArguments<EarlyWellKnownAttributeBinder, NamedTypeSymbol, AttributeSyntax, AttributeLocation> arguments) { Debug.Assert(arguments.SymbolPart == AttributeLocation.None || arguments.SymbolPart == AttributeLocation.Return); bool hasAnyDiagnostics; if (arguments.SymbolPart == AttributeLocation.None) { if (CSharpAttributeData.IsTargetEarlyAttribute(arguments.AttributeType, arguments.AttributeSyntax, AttributeDescription.ConditionalAttribute)) { var (attributeData, boundAttribute) = arguments.Binder.GetAttribute(arguments.AttributeSyntax, arguments.AttributeType, beforeAttributePartBound: null, afterAttributePartBound: null, out hasAnyDiagnostics); if (!attributeData.HasErrors) { string? name = attributeData.GetConstructorArgument<string>(0, SpecialType.System_String); arguments.GetOrCreateData<MethodEarlyWellKnownAttributeData>().AddConditionalSymbol(name); if (!hasAnyDiagnostics) { return (attributeData, boundAttribute); } } return (null, null); } else if (EarlyDecodeDeprecatedOrExperimentalOrObsoleteAttribute(ref arguments, out CSharpAttributeData? attributeData, out BoundAttribute? boundAttribute, out ObsoleteAttributeData? obsoleteData)) { if (obsoleteData != null) { arguments.GetOrCreateData<MethodEarlyWellKnownAttributeData>().ObsoleteAttributeData = obsoleteData; } return (attributeData, boundAttribute); } else if (CSharpAttributeData.IsTargetEarlyAttribute(arguments.AttributeType, arguments.AttributeSyntax, AttributeDescription.UnmanagedCallersOnlyAttribute)) { arguments.GetOrCreateData<MethodEarlyWellKnownAttributeData>().UnmanagedCallersOnlyAttributePresent = true; // We can't actually decode this attribute yet: CallConvs is an array, and it cannot be bound yet or we could hit a cycle // in error cases. We only detect whether or not the attribute is present for use in ensuring that we create as few lazily-computed // diagnostics that might later get thrown away as possible when binding method calls. return (null, null); } else if (CSharpAttributeData.IsTargetEarlyAttribute(arguments.AttributeType, arguments.AttributeSyntax, AttributeDescription.OverloadResolutionPriorityAttribute)) { if (!CanHaveOverloadResolutionPriority) { // Cannot use 'OverloadResolutionPriorityAttribute' on this member. return (null, null); } (attributeData, boundAttribute) = arguments.Binder.GetAttribute(arguments.AttributeSyntax, arguments.AttributeType, beforeAttributePartBound: null, afterAttributePartBound: null, out hasAnyDiagnostics); if (attributeData.CommonConstructorArguments is [{ ValueInternal: int priority }]) { arguments.GetOrCreateData<MethodEarlyWellKnownAttributeData>().OverloadResolutionPriority = priority; if (!hasAnyDiagnostics) { return (attributeData, boundAttribute); } } return (null, null); } } return base.EarlyDecodeWellKnownAttribute(ref arguments); } /// <summary> /// Binds attributes applied to this method. /// </summary> public ImmutableArray<(CSharpAttributeData, BoundAttribute)> BindMethodAttributes() { return BindAttributes(GetAttributeDeclarations(), OuterBinder); } #nullable disable public override bool AreLocalsZeroed { get { var data = this.GetDecodedWellKnownAttributeData(); return data?.HasSkipLocalsInitAttribute != true && AreContainingSymbolLocalsZeroed; } } /// <summary> /// Returns data decoded from Obsolete attribute or null if there is no Obsolete attribute. /// This property returns ObsoleteAttributeData.Uninitialized if attribute arguments haven't been decoded yet. /// </summary> internal sealed override ObsoleteAttributeData ObsoleteAttributeData { get { if (ContainingSymbol is SourceMemberContainerTypeSymbol { AnyMemberHasAttributes: false }) { return null; } var lazyCustomAttributesBag = _lazyCustomAttributesBag; if (lazyCustomAttributesBag != null && lazyCustomAttributesBag.IsEarlyDecodedWellKnownAttributeDataComputed) { var data = (MethodEarlyWellKnownAttributeData)lazyCustomAttributesBag.EarlyDecodedWellKnownAttributeData; return data != null ? data.ObsoleteAttributeData : null; } if (syntaxReferenceOpt is null) { // no references -> no attributes return null; } return ObsoleteAttributeData.Uninitialized; } } #nullable enable internal sealed override UnmanagedCallersOnlyAttributeData? GetUnmanagedCallersOnlyAttributeData(bool forceComplete) { if (syntaxReferenceOpt is null) { // no references -> no attributes return null; } if (forceComplete) { _ = this.GetAttributes(); } var lazyCustomAttributesBag = _lazyCustomAttributesBag; if (lazyCustomAttributesBag is null || !lazyCustomAttributesBag.IsEarlyDecodedWellKnownAttributeDataComputed) { Debug.Assert(!forceComplete); return UnmanagedCallersOnlyAttributeData.Uninitialized; } if (lazyCustomAttributesBag.IsDecodedWellKnownAttributeDataComputed) { var lateData = (MethodWellKnownAttributeData?)lazyCustomAttributesBag.DecodedWellKnownAttributeData; #if DEBUG verifyDataConsistent((MethodEarlyWellKnownAttributeData?)lazyCustomAttributesBag.EarlyDecodedWellKnownAttributeData, lateData); #endif return lateData?.UnmanagedCallersOnlyAttributeData; } var earlyData = (MethodEarlyWellKnownAttributeData?)lazyCustomAttributesBag.EarlyDecodedWellKnownAttributeData; Debug.Assert(!forceComplete); return earlyData?.UnmanagedCallersOnlyAttributePresent == true ? UnmanagedCallersOnlyAttributeData.AttributePresentDataNotBound : null; #if DEBUG // Can remove ifdefs and replace with Conditional after https://github.com/dotnet/roslyn/issues/47463 is fixed static void verifyDataConsistent(MethodEarlyWellKnownAttributeData? earlyData, MethodWellKnownAttributeData? lateData) { if (lateData is { UnmanagedCallersOnlyAttributeData: not null }) { // We can't verify the symmetric case here. Error conditions (such as if a bad expression was provided to the array initializer) // can cause the attribute to be skipped during regular attribute binding. Early binding doesn't know that though, so // it still gets marked as present. Debug.Assert(earlyData is { UnmanagedCallersOnlyAttributePresent: true }); } else if (earlyData is null or { UnmanagedCallersOnlyAttributePresent: false }) { Debug.Assert(lateData is null or { UnmanagedCallersOnlyAttributeData: null }); } } #endif } #nullable disable internal sealed override ImmutableArray<string> GetAppliedConditionalSymbols() { MethodEarlyWellKnownAttributeData data = this.GetEarlyDecodedWellKnownAttributeData(); return data != null ? data.ConditionalSymbols : ImmutableArray<string>.Empty; } protected override void DecodeWellKnownAttributeImpl(ref DecodeWellKnownAttributeArguments<AttributeSyntax, CSharpAttributeData, AttributeLocation> arguments) { Debug.Assert(!arguments.Attribute.HasErrors); Debug.Assert(arguments.SymbolPart == AttributeLocation.None || arguments.SymbolPart == AttributeLocation.Return); if (arguments.SymbolPart == AttributeLocation.None) { DecodeWellKnownAttributeAppliedToMethod(ref arguments); } else { DecodeWellKnownAttributeAppliedToReturnValue(ref arguments); } } private void DecodeWellKnownAttributeAppliedToMethod(ref DecodeWellKnownAttributeArguments<AttributeSyntax, CSharpAttributeData, AttributeLocation> arguments) { Debug.Assert((object)arguments.AttributeSyntaxOpt != null); var attribute = arguments.Attribute; var diagnostics = (BindingDiagnosticBag)arguments.Diagnostics; Debug.Assert(!attribute.HasErrors); if (attribute.IsTargetAttribute(AttributeDescription.PreserveSigAttribute)) { arguments.GetOrCreateData<MethodWellKnownAttributeData>().SetPreserveSignature(arguments.Index); } else if (attribute.IsTargetAttribute(AttributeDescription.MethodImplAttribute)) { AttributeData.DecodeMethodImplAttribute<MethodWellKnownAttributeData, AttributeSyntax, CSharpAttributeData, AttributeLocation>(ref arguments, MessageProvider.Instance, this.ContainingType); } else if (attribute.IsTargetAttribute(AttributeDescription.DllImportAttribute)) { DecodeDllImportAttribute(ref arguments); } else if (attribute.IsTargetAttribute(AttributeDescription.SpecialNameAttribute)) { arguments.GetOrCreateData<MethodWellKnownAttributeData>().HasSpecialNameAttribute = true; } else if (attribute.IsTargetAttribute(AttributeDescription.ExcludeFromCodeCoverageAttribute)) { arguments.GetOrCreateData<MethodWellKnownAttributeData>().HasExcludeFromCodeCoverageAttribute = true; } else if (attribute.IsTargetAttribute(AttributeDescription.ConditionalAttribute)) { ValidateConditionalAttribute(attribute, arguments.AttributeSyntaxOpt, diagnostics); } else if (attribute.IsTargetAttribute(AttributeDescription.SuppressUnmanagedCodeSecurityAttribute)) { arguments.GetOrCreateData<MethodWellKnownAttributeData>().HasSuppressUnmanagedCodeSecurityAttribute = true; } else if (attribute.IsTargetAttribute(AttributeDescription.DynamicSecurityMethodAttribute)) { arguments.GetOrCreateData<MethodWellKnownAttributeData>().HasDynamicSecurityMethodAttribute = true; } else if (VerifyObsoleteAttributeAppliedToMethod(ref arguments, AttributeDescription.ObsoleteAttribute)) { } else if (VerifyObsoleteAttributeAppliedToMethod(ref arguments, AttributeDescription.DeprecatedAttribute)) { } else if (attribute.IsTargetAttribute(AttributeDescription.RequiresUnsafeAttribute)) { diagnostics.Add(ErrorCode.ERR_RequiresUnsafeAttributeInSource, arguments.AttributeSyntaxOpt.Location); } else if (ReportExplicitUseOfReservedAttributes( in arguments, permitted: ReservedAttributes.DynamicAttribute | ReservedAttributes.TupleElementNamesAttribute | ReservedAttributes.NullableAttribute | ReservedAttributes.NullablePublicOnlyAttribute | ReservedAttributes.NativeIntegerAttribute | ReservedAttributes.RequiredMemberAttribute | ReservedAttributes.ScopedRefAttribute | ReservedAttributes.RefSafetyRulesAttribute)) { } else if (attribute.IsTargetAttribute(AttributeDescription.SecurityCriticalAttribute) || attribute.IsTargetAttribute(AttributeDescription.SecuritySafeCriticalAttribute)) { if (IsAsync) { diagnostics.Add(ErrorCode.ERR_SecurityCriticalOrSecuritySafeCriticalOnAsync, arguments.AttributeSyntaxOpt.Location, arguments.AttributeSyntaxOpt.GetErrorDisplayName()); } } else if (attribute.IsTargetAttribute(AttributeDescription.SkipLocalsInitAttribute)) { CSharpAttributeData.DecodeSkipLocalsInitAttribute<MethodWellKnownAttributeData>(DeclaringCompilation, ref arguments); } else if (attribute.IsTargetAttribute(AttributeDescription.DoesNotReturnAttribute)) { arguments.GetOrCreateData<MethodWellKnownAttributeData>().HasDoesNotReturnAttribute = true; } else if (attribute.IsTargetAttribute(AttributeDescription.MemberNotNullAttribute)) { MessageID.IDS_FeatureMemberNotNull.CheckFeatureAvailability(diagnostics, arguments.AttributeSyntaxOpt); CSharpAttributeData.DecodeMemberNotNullAttribute<MethodWellKnownAttributeData>(ContainingType, ref arguments); } else if (attribute.IsTargetAttribute(AttributeDescription.MemberNotNullWhenAttribute)) { MessageID.IDS_FeatureMemberNotNull.CheckFeatureAvailability(diagnostics, arguments.AttributeSyntaxOpt); CSharpAttributeData.DecodeMemberNotNullWhenAttribute<MethodWellKnownAttributeData>(ContainingType, ref arguments); } else if (attribute.IsTargetAttribute(AttributeDescription.ModuleInitializerAttribute)) { MessageID.IDS_FeatureModuleInitializers.CheckFeatureAvailability(diagnostics, arguments.AttributeSyntaxOpt); DecodeModuleInitializerAttribute(arguments); } else if (attribute.IsTargetAttribute(AttributeDescription.UnmanagedCallersOnlyAttribute)) { DecodeUnmanagedCallersOnlyAttribute(ref arguments); } else if (attribute.IsTargetAttribute(AttributeDescription.UnscopedRefAttribute)) { if (!this.UseUpdatedEscapeRules) { diagnostics.Add(ErrorCode.WRN_UnscopedRefAttributeOldRules, arguments.AttributeSyntaxOpt.Location); } if (this.IsValidUnscopedRefAttributeTarget()) { arguments.GetOrCreateData<MethodWellKnownAttributeData>().HasUnscopedRefAttribute = true; if (ContainingType.IsInterface || IsExplicitInterfaceImplementation) { MessageID.IDS_FeatureRefStructInterfaces.CheckFeatureAvailability(diagnostics, arguments.AttributeSyntaxOpt); } } else { diagnostics.Add(ErrorCode.ERR_UnscopedRefAttributeUnsupportedMemberTarget, arguments.AttributeSyntaxOpt.Location); } } else if (attribute.IsTargetAttribute(AttributeDescription.InterceptsLocationAttribute)) { DecodeInterceptsLocationAttribute(arguments); } else if (attribute.IsTargetAttribute(AttributeDescription.OverloadResolutionPriorityAttribute)) { MessageID.IDS_FeatureOverloadResolutionPriority.CheckFeatureAvailability(diagnostics, arguments.AttributeSyntaxOpt); if (!CanHaveOverloadResolutionPriority) { diagnostics.Add(IsOverride // Cannot use 'OverloadResolutionPriorityAttribute' on an overriding member. ? ErrorCode.ERR_CannotApplyOverloadResolutionPriorityToOverride // Cannot use 'OverloadResolutionPriorityAttribute' on this member. : ErrorCode.ERR_CannotApplyOverloadResolutionPriorityToMember, arguments.AttributeSyntaxOpt); } } else if (attribute.IsTargetAttribute(AttributeDescription.RuntimeAsyncMethodGenerationAttribute)) { arguments.GetOrCreateData<MethodWellKnownAttributeData>().RuntimeAsyncMethodGenerationSetting = attribute.CommonConstructorArguments[0].DecodeValue<bool>(SpecialType.System_Boolean) ? ThreeState.True : ThreeState.False; } else { var compilation = this.DeclaringCompilation; if (attribute.IsSecurityAttribute(compilation)) { attribute.DecodeSecurityAttribute<MethodWellKnownAttributeData>(this, compilation, ref arguments); } } } internal override ThreeState RuntimeAsyncMethodGenerationAttributeSetting => GetDecodedWellKnownAttributeData()?.RuntimeAsyncMethodGenerationSetting ?? ThreeState.Unknown; internal override ImmutableArray<string> NotNullMembers => GetDecodedWellKnownAttributeData()?.NotNullMembers ?? ImmutableArray<string>.Empty; internal override ImmutableArray<string> NotNullWhenTrueMembers => GetDecodedWellKnownAttributeData()?.NotNullWhenTrueMembers ?? ImmutableArray<string>.Empty; internal override ImmutableArray<string> NotNullWhenFalseMembers => GetDecodedWellKnownAttributeData()?.NotNullWhenFalseMembers ?? ImmutableArray<string>.Empty; public override FlowAnalysisAnnotations FlowAnalysisAnnotations { get { return DecodeFlowAnalysisAttributes(GetDecodedWellKnownAttributeData()); } } private static FlowAnalysisAnnotations DecodeFlowAnalysisAttributes(MethodWellKnownAttributeData attributeData) => attributeData?.HasDoesNotReturnAttribute == true ? FlowAnalysisAnnotations.DoesNotReturn : FlowAnalysisAnnotations.None; internal sealed override bool HasUnscopedRefAttribute => GetDecodedWellKnownAttributeData()?.HasUnscopedRefAttribute == true; private bool VerifyObsoleteAttributeAppliedToMethod( ref DecodeWellKnownAttributeArguments<AttributeSyntax, CSharpAttributeData, AttributeLocation> arguments, AttributeDescription description) { if (arguments.Attribute.IsTargetAttribute(description)) { if (this.IsAccessor()) { var diagnostics = (BindingDiagnosticBag)arguments.Diagnostics; if (this is SourceEventAccessorSymbol) { // CS1667: Attribute '{0}' is not valid on event accessors. It is only valid on '{1}' declarations. AttributeUsageInfo attributeUsage = arguments.Attribute.AttributeClass.GetAttributeUsageInfo(); diagnostics.Add(ErrorCode.ERR_AttributeNotOnEventAccessor, arguments.AttributeSyntaxOpt.Name.Location, description.FullName, attributeUsage.GetValidTargetsErrorArgument()); } else { MessageID.IDS_FeatureObsoleteOnPropertyAccessor.CheckFeatureAvailability(diagnostics, arguments.AttributeSyntaxOpt); } } return true; } return false; } private void ValidateConditionalAttribute(CSharpAttributeData attribute, AttributeSyntax node, BindingDiagnosticBag diagnostics) { Debug.Assert(this.IsConditional); if (this.IsAccessor()) { // CS1667: Attribute '{0}' is not valid on property or event accessors. It is only valid on '{1}' declarations. AttributeUsageInfo attributeUsage = attribute.AttributeClass.GetAttributeUsageInfo(); diagnostics.Add(ErrorCode.ERR_AttributeNotOnAccessor, node.Name.Location, node.GetErrorDisplayName(), attributeUsage.GetValidTargetsErrorArgument()); } else if (this.ContainingType.IsInterfaceType()) { // CS0582: The Conditional attribute is not valid on interface members diagnostics.Add(ErrorCode.ERR_ConditionalOnInterfaceMethod, node.Location); } else if (this.IsOverride) { // CS0243: The Conditional attribute is not valid on '{0}' because it is an override method diagnostics.Add(ErrorCode.ERR_ConditionalOnOverride, node.Location, this); } else if (!this.CanBeReferencedByName || this.MethodKind == MethodKind.Destructor) { // CS0577: The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, lambda expression, or explicit interface implementation diagnostics.Add(ErrorCode.ERR_ConditionalOnSpecialMethod, node.Location, this); } else if (!this.ReturnsVoid) { // CS0578: The Conditional attribute is not valid on '{0}' because its return type is not void diagnostics.Add(ErrorCode.ERR_ConditionalMustReturnVoid, node.Location, this); } else if (this.HasAnyOutParameter()) { // CS0685: Conditional member '{0}' cannot have an out parameter diagnostics.Add(ErrorCode.ERR_ConditionalWithOutParam, node.Location, this); } else if (this is { MethodKind: MethodKind.LocalFunction, IsStatic: false }) { diagnostics.Add(ErrorCode.ERR_ConditionalOnLocalFunction, node.Location, this); } else { string name = attribute.GetConstructorArgument<string>(0, SpecialType.System_String); if (name == null || !SyntaxFacts.IsValidIdentifier(name)) { // CS0633: The argument to the '{0}' attribute must be a valid identifier diagnostics.Add(ErrorCode.ERR_BadArgumentToAttribute, attribute.GetAttributeArgumentLocation(0), node.GetErrorDisplayName()); } } } private bool HasAnyOutParameter() { foreach (var param in this.Parameters) { if (param.RefKind == RefKind.Out) { return true; } } return false; } private void DecodeWellKnownAttributeAppliedToReturnValue(ref DecodeWellKnownAttributeArguments<AttributeSyntax, CSharpAttributeData, AttributeLocation> arguments) { Debug.Assert((object)arguments.AttributeSyntaxOpt != null); var attribute = arguments.Attribute; var diagnostics = (BindingDiagnosticBag)arguments.Diagnostics; Debug.Assert(!attribute.HasErrors); if (attribute.IsTargetAttribute(AttributeDescription.MarshalAsAttribute)) { // MarshalAs applied to the return value: MarshalAsAttributeDecoder<ReturnTypeWellKnownAttributeData, AttributeSyntax, CSharpAttributeData, AttributeLocation>.Decode(ref arguments, AttributeTargets.ReturnValue, MessageProvider.Instance); } else if (ReportExplicitUseOfReservedAttributes( in arguments, permitted: ReservedAttributes.NullableContextAttribute | ReservedAttributes.NullablePublicOnlyAttribute | ReservedAttributes.CaseSensitiveExtensionAttribute | ReservedAttributes.RequiredMemberAttribute | ReservedAttributes.ScopedRefAttribute | ReservedAttributes.RefSafetyRulesAttribute)) { } else if (attribute.IsTargetAttribute(AttributeDescription.MaybeNullAttribute)) { arguments.GetOrCreateData<ReturnTypeWellKnownAttributeData>().HasMaybeNullAttribute = true; } else if (attribute.IsTargetAttribute(AttributeDescription.NotNullAttribute)) { arguments.GetOrCreateData<ReturnTypeWellKnownAttributeData>().HasNotNullAttribute = true; } else if (attribute.IsTargetAttribute(AttributeDescription.NotNullIfNotNullAttribute)) { arguments.GetOrCreateData<ReturnTypeWellKnownAttributeData>().AddNotNullIfParameterNotNull(attribute.DecodeNotNullIfNotNullAttribute()); } } #nullable enable private void DecodeDllImportAttribute(ref DecodeWellKnownAttributeArguments<AttributeSyntax, CSharpAttributeData, AttributeLocation> arguments) { RoslynDebug.Assert(arguments.AttributeSyntaxOpt?.ArgumentList is object); var attribute = arguments.Attribute; var diagnostics = (BindingDiagnosticBag)arguments.Diagnostics; Debug.Assert(!attribute.HasErrors); bool hasErrors = false; var implementationPart = this.PartialImplementationPart ?? this; if (!implementationPart.IsExtern || (!implementationPart.IsStatic && !implementationPart.IsExtensionBlockMember())) { diagnostics.Add(ErrorCode.ERR_DllImportOnInvalidMethod, arguments.AttributeSyntaxOpt.Name.Location); hasErrors = true; } var isAnyNestedMethodGeneric = false; for (MethodSymbol? current = this; current is object; current = current.ContainingSymbol as MethodSymbol) { if (current.IsGenericMethod) { isAnyNestedMethodGeneric = true; break; } } if (isAnyNestedMethodGeneric || ContainingType?.IsGenericType == true) { diagnostics.Add(ErrorCode.ERR_DllImportOnGenericMethod, arguments.AttributeSyntaxOpt.Name.Location); hasErrors = true; } string? moduleName = attribute.GetConstructorArgument<string>(0, SpecialType.System_String); if (!MetadataHelpers.IsValidMetadataIdentifier(moduleName)) { // Dev10 reports CS0647: "Error emitting attribute ..." diagnostics.Add(ErrorCode.ERR_InvalidAttributeArgument, attribute.GetAttributeArgumentLocation(0), arguments.AttributeSyntaxOpt.GetErrorDisplayName()); hasErrors = true; moduleName = null; } // Default value of charset is inherited from the module (only if specified). // This might be different from ContainingType.DefaultMarshallingCharSet. If the charset is not specified on module // ContainingType.DefaultMarshallingCharSet would be Ansi (the class is emitted with "Ansi" charset metadata flag) // while the charset in P/Invoke metadata should be "None". CharSet charSet = this.GetEffectiveDefaultMarshallingCharSet() ?? Cci.Constants.CharSet_None; string? importName = null; bool preserveSig = true; CallingConvention callingConvention = System.Runtime.InteropServices.CallingConvention.Winapi; bool setLastError = false; bool exactSpelling = false; // C#: ExactSpelling=false for any charset bool? bestFitMapping = null; bool? throwOnUnmappable = null; int position = 1; foreach (var namedArg in attribute.CommonNamedArguments) { switch (namedArg.Key) { case "EntryPoint": importName = namedArg.Value.ValueInternal as string; if (!MetadataHelpers.IsValidMetadataIdentifier(importName)) { // Dev10 reports CS0647: "Error emitting attribute ..." diagnostics.Add(ErrorCode.ERR_InvalidNamedArgument, arguments.AttributeSyntaxOpt.ArgumentList.Arguments[position].Location, namedArg.Key); hasErrors = true; importName = null; } break; case "CharSet": // invalid values will be ignored charSet = namedArg.Value.DecodeValue<CharSet>(SpecialType.System_Enum); break; case "SetLastError": // invalid values will be ignored setLastError = namedArg.Value.DecodeValue<bool>(SpecialType.System_Boolean); break; case "ExactSpelling": // invalid values will be ignored exactSpelling = namedArg.Value.DecodeValue<bool>(SpecialType.System_Boolean); break; case "PreserveSig": preserveSig = namedArg.Value.DecodeValue<bool>(SpecialType.System_Boolean); break; case "CallingConvention": // invalid values will be ignored callingConvention = namedArg.Value.DecodeValue<CallingConvention>(SpecialType.System_Enum); break; case "BestFitMapping": bestFitMapping = namedArg.Value.DecodeValue<bool>(SpecialType.System_Boolean); break; case "ThrowOnUnmappableChar": throwOnUnmappable = namedArg.Value.DecodeValue<bool>(SpecialType.System_Boolean); break; } position++; } if (!hasErrors) { arguments.GetOrCreateData<MethodWellKnownAttributeData>().SetDllImport( arguments.Index, moduleName, importName ?? Name, DllImportData.MakeFlags( exactSpelling, charSet, setLastError, callingConvention, bestFitMapping, throwOnUnmappable), preserveSig); } } private void DecodeModuleInitializerAttribute(DecodeWellKnownAttributeArguments<AttributeSyntax, CSharpAttributeData, AttributeLocation> arguments) { Debug.Assert(arguments.AttributeSyntaxOpt is object); var diagnostics = (BindingDiagnosticBag)arguments.Diagnostics; if (MethodKind != MethodKind.Ordinary || this.IsExtensionBlockMember()) { diagnostics.Add(ErrorCode.ERR_ModuleInitializerMethodMustBeOrdinary, arguments.AttributeSyntaxOpt.Location); return; } Debug.Assert(ContainingType is object); var hasError = false; var useSiteInfo = new CompoundUseSiteInfo<AssemblySymbol>(diagnostics, ContainingAssembly); if (!AccessCheck.IsSymbolAccessible(this, ContainingAssembly, ref useSiteInfo)) { diagnostics.Add(ErrorCode.ERR_ModuleInitializerMethodMustBeAccessibleOutsideTopLevelType, arguments.AttributeSyntaxOpt.Location, Name); hasError = true; } diagnostics.Add(arguments.AttributeSyntaxOpt, useSiteInfo); if (!IsStatic || ParameterCount > 0 || !ReturnsVoid || IsAbstract || IsVirtual) { diagnostics.Add(ErrorCode.ERR_ModuleInitializerMethodMustBeStaticParameterlessVoid, arguments.AttributeSyntaxOpt.Location, Name); hasError = true; } if (IsGenericMethod || ContainingType.IsGenericType) { diagnostics.Add(ErrorCode.ERR_ModuleInitializerMethodAndContainingTypesMustNotBeGeneric, arguments.AttributeSyntaxOpt.Location, Name); hasError = true; } // If this is an UnmanagedCallersOnly method, it means that this cannot be called by managed code, including the attempt by the CLR // to run the module initializer. if (_lazyCustomAttributesBag.EarlyDecodedWellKnownAttributeData is MethodEarlyWellKnownAttributeData { UnmanagedCallersOnlyAttributePresent: true }) { diagnostics.Add(ErrorCode.ERR_ModuleInitializerCannotBeUnmanagedCallersOnly, arguments.AttributeSyntaxOpt.Location); hasError = true; } if (!hasError && !CallsAreOmitted(arguments.AttributeSyntaxOpt.SyntaxTree)) { DeclaringCompilation.AddModuleInitializerMethod(this); } } private void DecodeInterceptsLocationAttribute(DecodeWellKnownAttributeArguments<AttributeSyntax, CSharpAttributeData, AttributeLocation> arguments) { Debug.Assert(!arguments.Attribute.HasErrors); var constructorArguments = arguments.Attribute.CommonConstructorArguments; if (constructorArguments is [ { Type.SpecialType: SpecialType.System_String }, { Kind: not TypedConstantKind.Array, Value: int lineNumberOneBased }, { Kind: not TypedConstantKind.Array, Value: int characterNumberOneBased }]) { DecodeInterceptsLocationAttributeExperimentalCompat(arguments, attributeFilePath: (string?)constructorArguments[0].Value, lineNumberOneBased, characterNumberOneBased); } else { Debug.Assert(arguments.Attribute.AttributeConstructor.Parameters is [{ Type.SpecialType: SpecialType.System_Int32 }, { Type.SpecialType: SpecialType.System_String }]); DecodeInterceptsLocationChecksumBased(arguments, version: (int)constructorArguments[0].Value!, data: (string?)constructorArguments[1].Value); } } private void DecodeInterceptsLocationChecksumBased(DecodeWellKnownAttributeArguments<AttributeSyntax, CSharpAttributeData, AttributeLocation> arguments, int version, string? data) { var diagnostics = (BindingDiagnosticBag)arguments.Diagnostics; Debug.Assert(arguments.AttributeSyntaxOpt is not null); var attributeNameSyntax = arguments.AttributeSyntaxOpt.Name; // used for reporting diagnostics var attributeLocation = attributeNameSyntax.Location; if (version != 1) { diagnostics.Add(ErrorCode.ERR_InterceptsLocationUnsupportedVersion, attributeLocation, version); return; } if (InterceptableLocation1.Decode(data) is not var (hash, position, displayFileName)) { diagnostics.Add(ErrorCode.ERR_InterceptsLocationDataInvalidFormat, attributeLocation); return; } var interceptorsNamespaces = ((CSharpParseOptions)attributeNameSyntax.SyntaxTree.Options).InterceptorsNamespaces; var thisNamespaceNames = getNamespaceNames(this); var foundAnyMatch = interceptorsNamespaces.Any(static (ns, thisNamespaceNames) => isDeclaredInNamespace(thisNamespaceNames, ns), thisNamespaceNames); if (!foundAnyMatch) { reportFeatureNotEnabled(diagnostics, attributeLocation, thisNamespaceNames); thisNamespaceNames.Free(); return; } thisNamespaceNames.Free(); bool hadError = ReportBadInterceptsLocation(diagnostics, attributeLocation); if (hadError) { return; } var matchingTrees = DeclaringCompilation.GetSyntaxTreesByContentHash(hash); if (matchingTrees.Count > 1) { diagnostics.Add(ErrorCode.ERR_InterceptsLocationDuplicateFile, attributeLocation, displayFileName); return; } if (matchingTrees.Count == 0) { diagnostics.Add(ErrorCode.ERR_InterceptsLocationFileNotFound, attributeLocation, displayFileName); return; } Debug.Assert(matchingTrees.Count == 1); SyntaxTree? matchingTree = matchingTrees[0]; var root = matchingTree.GetRoot(); if (position < 0 || position > root.EndPosition) { diagnostics.Add(ErrorCode.ERR_InterceptsLocationDataInvalidPosition, attributeLocation, displayFileName); return; } var referencedLines = matchingTree.GetText().Lines; var referencedLineCount = referencedLines.Count; var referencedToken = root.FindToken(position); switch (referencedToken) { case { Parent: SimpleNameSyntax { Parent: MemberAccessExpressionSyntax { Parent: InvocationExpressionSyntax } memberAccess } rhs } when memberAccess.Name == rhs: case { Parent: SimpleNameSyntax { Parent: MemberBindingExpressionSyntax { Parent: InvocationExpressionSyntax } memberBinding } rhs1 } when memberBinding.Name == rhs1: case { Parent: SimpleNameSyntax { Parent: InvocationExpressionSyntax invocation } simpleName } when invocation.Expression == simpleName: // happy case break; case { Parent: SimpleNameSyntax { Parent: not (MemberAccessExpressionSyntax or MemberBindingExpressionSyntax) } }: case { Parent: SimpleNameSyntax { Parent: MemberAccessExpressionSyntax memberAccess } rhs } when memberAccess.Name == rhs: case { Parent: SimpleNameSyntax { Parent: MemberBindingExpressionSyntax memberBinding } rhs1 } when memberBinding.Name == rhs1: // NB: there are all sorts of places "simple names" can appear in syntax. With these checks we are trying to // minimize confusion about why the name being used is not *interceptable*, but it's done on a best-effort basis. diagnostics.Add(ErrorCode.ERR_InterceptorNameNotInvoked, attributeLocation, referencedToken.Text); return; default: diagnostics.Add(ErrorCode.ERR_InterceptorPositionBadToken, attributeLocation, referencedToken.Text); return; } if (position != referencedToken.Position) { diagnostics.Add(ErrorCode.ERR_InterceptsLocationDataInvalidPosition, attributeLocation, displayFileName); return; } DeclaringCompilation.AddInterception(matchingTree.GetText().GetContentHash(), position, attributeLocation, this); // Caller must free the returned builder. static ArrayBuilder<string> getNamespaceNames(SourceMethodSymbol @this) { var namespaceNames = ArrayBuilder<string>.GetInstance(); for (var containingNamespace = @this.ContainingNamespace; containingNamespace?.IsGlobalNamespace == false; containingNamespace = containingNamespace.ContainingNamespace) namespaceNames.Add(containingNamespace.Name); // order outermost->innermost // e.g. for method MyApp.Generated.Interceptors.MyInterceptor(): ["MyApp", "Generated", "Interceptors"] namespaceNames.ReverseContents(); return namespaceNames; } static bool isDeclaredInNamespace(ArrayBuilder<string> thisNamespaceNames, ImmutableArray<string> namespaceSegments) { Debug.Assert(namespaceSegments.Length > 0); if (namespaceSegments is ["global"]) { return true; } if (namespaceSegments.Length > thisNamespaceNames.Count) { // the enabled NS has more components than interceptor's NS, so it will never match. return false; } for (var i = 0; i < namespaceSegments.Length; i++) { if (namespaceSegments[i] != thisNamespaceNames[i]) { return false; } } return true; } static void reportFeatureNotEnabled(BindingDiagnosticBag diagnostics, Location attributeLocation, ArrayBuilder<string> namespaceNames) { if (namespaceNames.Count == 0) { diagnostics.Add(ErrorCode.ERR_InterceptorGlobalNamespace, attributeLocation); } else { var recommendedProperty = $"<InterceptorsNamespaces>$(InterceptorsNamespaces);{string.Join(".", namespaceNames)}</InterceptorsNamespaces>"; diagnostics.Add(ErrorCode.ERR_InterceptorsFeatureNotEnabled, attributeLocation, recommendedProperty); } } } private void DecodeInterceptsLocationAttributeExperimentalCompat( DecodeWellKnownAttributeArguments<AttributeSyntax, CSharpAttributeData, AttributeLocation> arguments, string? attributeFilePath, int lineNumberOneBased, int characterNumberOneBased) { var diagnostics = (BindingDiagnosticBag)arguments.Diagnostics; var attributeSyntax = arguments.AttributeSyntaxOpt; Debug.Assert(attributeSyntax is object); var attributeLocation = attributeSyntax.Location; diagnostics.Add(ErrorCode.WRN_InterceptsLocationAttributeUnsupportedSignature, attributeLocation); const int filePathParameterIndex = 0; const int lineNumberParameterIndex = 1; const int characterNumberParameterIndex = 2; var interceptorsNamespaces = ((CSharpParseOptions)attributeSyntax.SyntaxTree.Options).InterceptorsNamespaces; var thisNamespaceNames = getNamespaceNames(); var foundAnyMatch = interceptorsNamespaces.Any(ns => isDeclaredInNamespace(thisNamespaceNames, ns)); if (!foundAnyMatch) { reportFeatureNotEnabled(diagnostics, attributeSyntax, thisNamespaceNames); thisNamespaceNames.Free(); return; } thisNamespaceNames.Free(); var attributeData = arguments.Attribute; if (attributeFilePath is null) { diagnostics.Add(ErrorCode.ERR_InterceptorFilePathCannotBeNull, attributeData.GetAttributeArgumentLocation(filePathParameterIndex)); return; } bool hadError = ReportBadInterceptsLocation(diagnostics, attributeLocation); if (hadError) { return; } var normalizedPath = FileUtilities.GetNormalizedPathOrOriginalPath(attributeFilePath, basePath: SyntaxTree.FilePath); var matchingTrees = DeclaringCompilation.GetSyntaxTreesByPath(normalizedPath); if (matchingTrees.Count > 1) { diagnostics.Add(ErrorCode.ERR_InterceptorNonUniquePath, attributeData.GetAttributeArgumentLocation(filePathParameterIndex), normalizedPath); return; } if (matchingTrees.Count == 0) { // Temporary compat behavior: check if 'attributeFilePath' is equivalent to a mapped path of one of the syntax trees in the compilation. matchingTrees = DeclaringCompilation.GetSyntaxTreesByMappedPath(attributeFilePath); if (matchingTrees.Count > 1) { diagnostics.Add(ErrorCode.ERR_InterceptorNonUniquePath, attributeData.GetAttributeArgumentLocation(filePathParameterIndex), attributeFilePath); return; } } // Neither the primary or compat methods of resolving the file path found any match. if (matchingTrees.Count == 0) { // if we expect '/src/Program.cs': // we might get: 'Program.cs' <-- suffix match var syntaxTrees = DeclaringCompilation.SyntaxTrees; var suffixMatch = syntaxTrees.FirstOrDefault(static (tree, attributeFilePathWithForwardSlashes) => tree.FilePath .Replace('\\', '/') .EndsWith(attributeFilePathWithForwardSlashes), attributeFilePath.Replace('\\', '/')); if (suffixMatch != null) { var recommendedPath = PathUtilities.IsAbsolute(SyntaxTree.FilePath) ? PathUtilities.GetRelativePath(PathUtilities.GetDirectoryName(SyntaxTree.FilePath), suffixMatch.FilePath) : suffixMatch.FilePath; diagnostics.Add( ErrorCode.ERR_InterceptorPathNotInCompilationWithCandidate, attributeData.GetAttributeArgumentLocation(filePathParameterIndex), attributeFilePath, recommendedPath); return; } diagnostics.Add(ErrorCode.ERR_InterceptorPathNotInCompilation, attributeData.GetAttributeArgumentLocation(filePathParameterIndex), normalizedPath); return; } Debug.Assert(matchingTrees.Count == 1); SyntaxTree? matchingTree = matchingTrees[0]; // Internally, line and character numbers are 0-indexed, but when they appear in code or diagnostic messages, they are 1-indexed. int lineNumberZeroBased = lineNumberOneBased - 1; int characterNumberZeroBased = characterNumberOneBased - 1; if (lineNumberZeroBased < 0 || characterNumberZeroBased < 0) { var location = attributeData.GetAttributeArgumentLocation(lineNumberZeroBased < 0 ? lineNumberParameterIndex : characterNumberParameterIndex); diagnostics.Add(ErrorCode.ERR_InterceptorLineCharacterMustBePositive, location); return; } var referencedLines = matchingTree.GetText().Lines; var referencedLineCount = referencedLines.Count; if (lineNumberZeroBased >= referencedLineCount) { diagnostics.Add(ErrorCode.ERR_InterceptorLineOutOfRange, attributeData.GetAttributeArgumentLocation(lineNumberParameterIndex), referencedLineCount, lineNumberOneBased); return; } var line = referencedLines[lineNumberZeroBased]; var lineLength = line.End - line.Start; if (characterNumberZeroBased >= lineLength) { diagnostics.Add(ErrorCode.ERR_InterceptorCharacterOutOfRange, attributeData.GetAttributeArgumentLocation(characterNumberParameterIndex), lineLength, characterNumberOneBased); return; } var referencedPosition = line.Start + characterNumberZeroBased; var root = matchingTree.GetRoot(); var referencedToken = root.FindToken(referencedPosition); switch (referencedToken) { case { Parent: SimpleNameSyntax { Parent: MemberAccessExpressionSyntax { Parent: InvocationExpressionSyntax } memberAccess } rhs } when memberAccess.Name == rhs: case { Parent: SimpleNameSyntax { Parent: InvocationExpressionSyntax invocation } simpleName } when invocation.Expression == simpleName: // happy case break; case { Parent: SimpleNameSyntax { Parent: not MemberAccessExpressionSyntax } }: case { Parent: SimpleNameSyntax { Parent: MemberAccessExpressionSyntax memberAccess } rhs } when memberAccess.Name == rhs: // NB: there are all sorts of places "simple names" can appear in syntax. With these checks we are trying to // minimize confusion about why the name being used is not *interceptable*, but it's done on a best-effort basis. diagnostics.Add(ErrorCode.ERR_InterceptorNameNotInvoked, attributeLocation, referencedToken.Text); return; default: diagnostics.Add(ErrorCode.ERR_InterceptorPositionBadToken, attributeLocation, referencedToken.Text); return; } // Did they actually refer to the start of the token, not the middle, or in trivia? // NB: here we don't want the provided position to refer to the start of token's leading trivia, in the checksum-based way we *do* want it to refer to the start of leading trivia (i.e. the Position) if (referencedPosition != referencedToken.SpanStart) { var linePositionZeroBased = referencedToken.GetLocation().GetLineSpan().StartLinePosition; diagnostics.Add(ErrorCode.ERR_InterceptorMustReferToStartOfTokenPosition, attributeLocation, referencedToken.Text, linePositionZeroBased.Line + 1, linePositionZeroBased.Character + 1); return; } DeclaringCompilation.AddInterception(matchingTree.GetText().GetContentHash(), referencedToken.Position, attributeLocation, this); // Caller must free the returned builder. ArrayBuilder<string> getNamespaceNames() { var namespaceNames = ArrayBuilder<string>.GetInstance(); for (var containingNamespace = ContainingNamespace; containingNamespace?.IsGlobalNamespace == false; containingNamespace = containingNamespace.ContainingNamespace) namespaceNames.Add(containingNamespace.Name); // order outermost->innermost // e.g. for method MyApp.Generated.Interceptors.MyInterceptor(): ["MyApp", "Generated", "Interceptors"] namespaceNames.ReverseContents(); return namespaceNames; } static bool isDeclaredInNamespace(ArrayBuilder<string> thisNamespaceNames, ImmutableArray<string> namespaceSegments) { Debug.Assert(namespaceSegments.Length > 0); if (namespaceSegments is ["global"]) { return true; } if (namespaceSegments.Length > thisNamespaceNames.Count) { // the enabled NS has more components than interceptor's NS, so it will never match. return false; } for (var i = 0; i < namespaceSegments.Length; i++) { if (namespaceSegments[i] != thisNamespaceNames[i]) { return false; } } return true; } static void reportFeatureNotEnabled(BindingDiagnosticBag diagnostics, AttributeSyntax attributeSyntax, ArrayBuilder<string> namespaceNames) { if (namespaceNames.Count == 0) { diagnostics.Add(ErrorCode.ERR_InterceptorGlobalNamespace, attributeSyntax); } else { var recommendedProperty = $"<InterceptorsNamespaces>$(InterceptorsNamespaces);{string.Join(".", namespaceNames)}</InterceptorsNamespaces>"; diagnostics.Add(ErrorCode.ERR_InterceptorsFeatureNotEnabled, attributeSyntax, recommendedProperty); } } } private bool ReportBadInterceptsLocation(BindingDiagnosticBag diagnostics, Location attributeLocation) { if (!this.IsExtensionBlockMember() && ContainingType.IsGenericType) { diagnostics.Add(ErrorCode.ERR_InterceptorContainingTypeCannotBeGeneric, attributeLocation, this); return true; } if (MethodKind != MethodKind.Ordinary) { diagnostics.Add(ErrorCode.ERR_InterceptorMethodMustBeOrdinary, attributeLocation); return true; } Debug.Assert(_lazyCustomAttributesBag.IsEarlyDecodedWellKnownAttributeDataComputed); var unmanagedCallersOnly = this.GetUnmanagedCallersOnlyAttributeData(forceComplete: false); if (unmanagedCallersOnly != null) { diagnostics.Add(ErrorCode.ERR_InterceptorCannotUseUnmanagedCallersOnly, attributeLocation); return true; } return false; } private void DecodeUnmanagedCallersOnlyAttribute(ref DecodeWellKnownAttributeArguments<AttributeSyntax, CSharpAttributeData, AttributeLocation> arguments) { Debug.Assert(arguments.AttributeSyntaxOpt != null); var diagnostics = (BindingDiagnosticBag)arguments.Diagnostics; arguments.GetOrCreateData<MethodWellKnownAttributeData>().UnmanagedCallersOnlyAttributeData = DecodeUnmanagedCallersOnlyAttributeData(this, arguments.Attribute, arguments.AttributeSyntaxOpt.Location, diagnostics); bool reportedError = CheckAndReportValidUnmanagedCallersOnlyTarget(arguments.AttributeSyntaxOpt.Name, diagnostics); var returnTypeSyntax = this.ExtractReturnTypeSyntax(); // If there is no return type (such as a property definition), Dummy.GetRoot() is returned. if (ReferenceEquals(returnTypeSyntax, CSharpSyntaxTree.Dummy.GetRoot())) { // If there's no syntax for the return type, then we already errored because this isn't a valid // unmanagedcallersonly target (it's a property getter/setter or some other non-regular-method). // Any more errors would just be noise. Debug.Assert(reportedError); return; } checkAndReportManagedTypes(ReturnType, this.RefKind, returnTypeSyntax, isParam: false, diagnostics); foreach (var param in Parameters) { checkAndReportManagedTypes(param.Type, param.RefKind, param.GetNonNullSyntaxNode(), isParam: true, diagnostics); } static void checkAndReportManagedTypes(TypeSymbol type, RefKind refKind, SyntaxNode syntax, bool isParam, BindingDiagnosticBag diagnostics) { if (refKind != RefKind.None) { diagnostics.Add(ErrorCode.ERR_CannotUseRefInUnmanagedCallersOnly, syntax.Location); } // use-site diagnostics will be reported at actual parameter declaration site, we're only interested // in reporting managed types being used switch (type.ManagedKindNoUseSiteDiagnostics) { case ManagedKind.Unmanaged: case ManagedKind.UnmanagedWithGenerics: // Note that this will let through some things that are technically unmanaged, but not // actually blittable. However, we don't have a formal concept of blittable in C# // itself, so checking for purely unmanaged types is the best we can do here. return; case ManagedKind.Managed: // Cannot use '{0}' as a {1} type on a method attributed with 'UnmanagedCallersOnly. diagnostics.Add(ErrorCode.ERR_CannotUseManagedTypeInUnmanagedCallersOnly, syntax.Location, type, (isParam ? MessageID.IDS_Parameter : MessageID.IDS_Return).Localize()); return; default: throw ExceptionUtilities.UnexpectedValue(type.ManagedKindNoUseSiteDiagnostics); } } static UnmanagedCallersOnlyAttributeData DecodeUnmanagedCallersOnlyAttributeData(SourceMethodSymbol @this, CSharpAttributeData attribute, Location location, BindingDiagnosticBag diagnostics) { Debug.Assert(attribute.AttributeClass is not null); ImmutableHashSet<CodeAnalysis.Symbols.INamedTypeSymbolInternal>? callingConventionTypes = null; if (attribute.CommonNamedArguments is { IsDefaultOrEmpty: false } namedArgs) { var systemType = @this.DeclaringCompilation.GetWellKnownType(WellKnownType.System_Type); foreach (var (key, value) in attribute.CommonNamedArguments) { // Technically, CIL can define a field and a property with the same name. However, such a // member results in an Ambiguous Member error, and we never get to this piece of code at all. // See UnmanagedCallersOnly_PropertyAndFieldNamedCallConvs for an example bool isField = attribute.AttributeClass.GetMembers(key).Any( static (m, systemType) => m is FieldSymbol { Type: ArrayTypeSymbol { ElementType: NamedTypeSymbol elementType } } && elementType.Equals(systemType, TypeCompareKind.ConsiderEverything), systemType); var namedArgumentDecoded = TryDecodeUnmanagedCallersOnlyCallConvsField(key, value, isField, location, diagnostics); if (namedArgumentDecoded.IsCallConvs) { callingConventionTypes = namedArgumentDecoded.CallConvs; } } } return UnmanagedCallersOnlyAttributeData.Create(callingConventionTypes); } } internal override void PostDecodeWellKnownAttributes(ImmutableArray<CSharpAttributeData> boundAttributes, ImmutableArray<AttributeSyntax> allAttributeSyntaxNodes, BindingDiagnosticBag diagnostics, AttributeLocation symbolPart, WellKnownAttributeData decodedData) { Debug.Assert(!boundAttributes.IsDefault); Debug.Assert(!allAttributeSyntaxNodes.IsDefault); Debug.Assert(boundAttributes.Length == allAttributeSyntaxNodes.Length); Debug.Assert(symbolPart == AttributeLocation.None || symbolPart == AttributeLocation.Return); if (symbolPart != AttributeLocation.Return) { Debug.Assert(_lazyCustomAttributesBag != null); Debug.Assert(_lazyCustomAttributesBag.IsDecodedWellKnownAttributeDataComputed); if (ContainingSymbol is NamedTypeSymbol { IsComImport: true, TypeKind: TypeKind.Class or TypeKind.Interface }) { switch (this.MethodKind) { case MethodKind.Constructor: case MethodKind.StaticConstructor: if (!this.IsImplicitlyDeclared) { // CS0669: A class with the ComImport attribute cannot have a user-defined constructor diagnostics.Add(ErrorCode.ERR_ComImportWithUserCtor, this.GetFirstLocation()); } break; default: if (!this.IsAbstract && !this.IsExtern) { // CS0423: Since '{1}' has the ComImport attribute, '{0}' must be extern or abstract diagnostics.Add(ErrorCode.ERR_ComImportWithImpl, this.GetFirstLocation(), this, ContainingType); } break; } } if (IsExtern && !IsAbstract && !this.IsPartialMember() && GetInMethodSyntaxNode() is null && boundAttributes.IsEmpty && !this.ContainingType.IsComImport) { var errorCode = (this.MethodKind == MethodKind.Constructor || this.MethodKind == MethodKind.StaticConstructor) ? ErrorCode.WRN_ExternCtorNoImplementation : ErrorCode.WRN_ExternMethodNoImplementation; diagnostics.Add(errorCode, this.GetFirstLocation(), this); } } base.PostDecodeWellKnownAttributes(boundAttributes, allAttributeSyntaxNodes, diagnostics, symbolPart, decodedData); } protected void AsyncMethodChecks(BindingDiagnosticBag diagnostics) { AsyncMethodChecks(verifyReturnType: true, this.GetFirstLocation(), diagnostics); } protected void AsyncMethodChecks(bool verifyReturnType, Location errorLocation, BindingDiagnosticBag diagnostics) { if (IsAsync) { bool hasErrors = false; if (verifyReturnType) { if (this.RefKind != RefKind.None) { var returnTypeSyntax = this.SyntaxNode switch { MethodDeclarationSyntax { ReturnType: var methodReturnType } => methodReturnType, LocalFunctionStatementSyntax { ReturnType: var localReturnType } => localReturnType, ParenthesizedLambdaExpressionSyntax { ReturnType: { } lambdaReturnType } => lambdaReturnType, var unexpected => throw ExceptionUtilities.UnexpectedValue(unexpected) }; ReportBadRefToken(returnTypeSyntax, diagnostics); hasErrors = true; } else if (isBadAsyncReturn(this)) { diagnostics.Add(ErrorCode.ERR_BadAsyncReturn, errorLocation); hasErrors = true; } } if (this.HasAsyncMethodBuilderAttribute(out _)) { MessageID.IDS_AsyncMethodBuilderOverride.CheckFeatureAvailability(diagnostics, this.DeclaringCompilation, errorLocation); } // Avoid checking attributes on containing types to avoid a potential cycle when a lambda // is used in an attribute argument - see https://github.com/dotnet/roslyn/issues/54074. // (ERR_SecurityCriticalOrSecuritySafeCriticalOnAsyncInClassOrStruct was never reported // for lambda expressions and is not a .NET Core scenario so it's not necessary to handle.) if (this.MethodKind != MethodKind.LambdaMethod) { for (NamedTypeSymbol curr = this.ContainingType; (object)curr != null; curr = curr.ContainingType) { if (curr is SourceNamedTypeSymbol { HasSecurityCriticalAttributes: true }) { diagnostics.Add(ErrorCode.ERR_SecurityCriticalOrSecuritySafeCriticalOnAsyncInClassOrStruct, errorLocation); hasErrors = true; break; } } } if ((this.ImplementationAttributes & System.Reflection.MethodImplAttributes.Synchronized) != 0) { diagnostics.Add(ErrorCode.ERR_SynchronizedAsyncMethod, errorLocation); hasErrors = true; } if (!hasErrors) { ReportAsyncParameterErrors(diagnostics, errorLocation); } var iAsyncEnumerableType = DeclaringCompilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerable_T); if (ReturnType.OriginalDefinition.Equals(iAsyncEnumerableType) && GetInMethodSyntaxNode() is object) { var cancellationTokenType = DeclaringCompilation.GetWellKnownType(WellKnownType.System_Threading_CancellationToken); var enumeratorCancellationCount = Parameters.Count(p => p.HasEnumeratorCancellationAttribute); if (enumeratorCancellationCount == 0 && ParameterTypesWithAnnotations.Any(static (p, cancellationTokenType) => p.Type.Equals(cancellationTokenType), cancellationTokenType)) { // Warn for CancellationToken parameters in async-iterators with no parameter decorated with [EnumeratorCancellation] // There could be more than one parameter that could be decorated with [EnumeratorCancellation] so we warn on the method instead diagnostics.Add(ErrorCode.WRN_UndecoratedCancellationTokenParameter, errorLocation, this); } if (enumeratorCancellationCount > 1) { // The [EnumeratorCancellation] attribute can only be used on one parameter diagnostics.Add(ErrorCode.ERR_MultipleEnumeratorCancellationAttributes, errorLocation); } } } static bool isBadAsyncReturn(MethodSymbol methodSymbol) { var returnType = methodSymbol.ReturnType; var declaringCompilation = methodSymbol.DeclaringCompilation; return !returnType.IsErrorType() && !returnType.IsVoidType() && !returnType.IsIAsyncEnumerableType(declaringCompilation) && !returnType.IsIAsyncEnumeratorType(declaringCompilation) && !methodSymbol.IsAsyncEffectivelyReturningTask(declaringCompilation) && !methodSymbol.IsAsyncEffectivelyReturningGenericTask(declaringCompilation); } } private static FlowAnalysisAnnotations DecodeReturnTypeAnnotationAttributes(ReturnTypeWellKnownAttributeData attributeData) { FlowAnalysisAnnotations annotations = FlowAnalysisAnnotations.None; if (attributeData != null) { if (attributeData.HasMaybeNullAttribute) { annotations |= FlowAnalysisAnnotations.MaybeNull; } if (attributeData.HasNotNullAttribute) { annotations |= FlowAnalysisAnnotations.NotNull; } } return annotations; } public sealed override bool HidesBaseMethodsByName { get { return false; } } internal sealed override bool HasRuntimeSpecialName { get { return base.HasRuntimeSpecialName || IsVtableGapInterfaceMethod(); } } private bool IsVtableGapInterfaceMethod() { return this.ContainingType.IsInterface && ModuleExtensions.GetVTableGapSize(this.MetadataName) > 0; } internal override bool HasSpecialName { get { switch (this.MethodKind) { case MethodKind.Constructor: case MethodKind.StaticConstructor: case MethodKind.PropertyGet: case MethodKind.PropertySet: case MethodKind.EventAdd: case MethodKind.EventRemove: case MethodKind.UserDefinedOperator: case MethodKind.Conversion: return true; } if (IsVtableGapInterfaceMethod()) { return true; } return HasSpecialNameAttribute; } } internal sealed override bool HasSpecialNameAttribute => GetDecodedWellKnownAttributeData()?.HasSpecialNameAttribute == true; internal sealed override bool IsDirectlyExcludedFromCodeCoverage => GetDecodedWellKnownAttributeData()?.HasExcludeFromCodeCoverageAttribute == true; internal override bool RequiresSecurityObject { get { var data = GetDecodedWellKnownAttributeData(); return data != null && data.HasDynamicSecurityMethodAttribute; } } internal override bool HasDeclarativeSecurity { get { var data = this.GetDecodedWellKnownAttributeData(); return data != null && data.HasDeclarativeSecurity; } } internal override IEnumerable<Cci.SecurityAttribute> GetSecurityInformation() { var attributesBag = this.GetAttributesBag(); var wellKnownData = (MethodWellKnownAttributeData)attributesBag.DecodedWellKnownAttributeData; if (wellKnownData != null) { SecurityWellKnownAttributeData? securityData = wellKnownData.SecurityInformation; if (securityData != null) { return securityData.GetSecurityAttributes(attributesBag.Attributes); } } return SpecializedCollections.EmptyEnumerable<Cci.SecurityAttribute>(); } public override DllImportData? GetDllImportData() { var data = this.GetDecodedWellKnownAttributeData(); return data != null ? data.DllImportPlatformInvokeData : null; } internal override MarshalPseudoCustomAttributeData? ReturnValueMarshallingInformation { get { var data = this.GetDecodedReturnTypeWellKnownAttributeData(); return data != null ? data.MarshallingInformation : null; } } internal override System.Reflection.MethodImplAttributes ImplementationAttributes { get { var data = GetDecodedWellKnownAttributeData(); var result = (data != null) ? data.MethodImplAttributes : default(System.Reflection.MethodImplAttributes); if (this.ContainingType.IsComImport && this.MethodKind == MethodKind.Constructor) { // Synthesized constructor of ComImport types is marked as Runtime implemented and InternalCall result |= (System.Reflection.MethodImplAttributes.Runtime | System.Reflection.MethodImplAttributes.InternalCall); } AddAsyncImplAttributeIfNeeded(ref result); return result; } } protected void AddAsyncImplAttributeIfNeeded(ref System.Reflection.MethodImplAttributes result) { if (this.IsAsync && this.DeclaringCompilation.IsRuntimeAsyncEnabledIn(this)) { // When a method is emitted using runtime async, we add MethodImplAttributes.Async to indicate to the // runtime to generate the state machine result |= System.Reflection.MethodImplAttributes.Async; } } internal override int TryGetOverloadResolutionPriority() => GetEarlyDecodedWellKnownAttributeData()?.OverloadResolutionPriority ?? 0; } }