|
// 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.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis.Symbols;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis
{
public abstract class AttributeData
{
protected AttributeData()
{
}
/// <summary>
/// The attribute class.
/// </summary>
public INamedTypeSymbol? AttributeClass { get { return CommonAttributeClass; } }
protected abstract INamedTypeSymbol? CommonAttributeClass { get; }
/// <summary>
/// The constructor on the attribute class.
/// </summary>
public IMethodSymbol? AttributeConstructor { get { return CommonAttributeConstructor; } }
protected abstract IMethodSymbol? CommonAttributeConstructor { get; }
public SyntaxReference? ApplicationSyntaxReference { get { return CommonApplicationSyntaxReference; } }
protected abstract SyntaxReference? CommonApplicationSyntaxReference { get; }
/// <summary>
/// Constructor arguments on the attribute.
/// </summary>
public ImmutableArray<TypedConstant> ConstructorArguments { get { return CommonConstructorArguments; } }
protected internal abstract ImmutableArray<TypedConstant> CommonConstructorArguments { get; }
/// <summary>
/// Named (property value) arguments on the attribute.
/// </summary>
public ImmutableArray<KeyValuePair<string, TypedConstant>> NamedArguments { get { return CommonNamedArguments; } }
protected internal abstract ImmutableArray<KeyValuePair<string, TypedConstant>> CommonNamedArguments { get; }
/// <summary>
/// Attribute is conditionally omitted if it is a source attribute and both the following are true:
/// (a) It has at least one applied conditional attribute AND
/// (b) None of conditional symbols are true at the attribute source location.
/// </summary>
internal virtual bool IsConditionallyOmitted
{
get { return false; }
}
// Uncommenting portion of the attribute is tracked by https://github.com/dotnet/roslyn/issues/70592
[MemberNotNullWhen(false, nameof(AttributeClass)/*, nameof(AttributeConstructor)*/)]
internal virtual bool HasErrors
{
get
{
Debug.Assert(AttributeClass is not null);
return false;
}
}
/// <summary>
/// Checks if an applied attribute with the given attributeType matches the namespace name and type name of the given early attribute's description
/// and the attribute description has a signature with parameter count equal to the given attributeArgCount.
/// NOTE: We don't allow early decoded attributes to have optional parameters.
/// </summary>
internal static bool IsTargetEarlyAttribute(INamedTypeSymbolInternal attributeType, int attributeArgCount, AttributeDescription description)
{
if (attributeType.ContainingSymbol?.Kind != SymbolKind.Namespace)
{
return false;
}
int attributeCtorsCount = description.Signatures.Length;
for (int i = 0; i < attributeCtorsCount; i++)
{
int parameterCount = description.GetParameterCount(signatureIndex: i);
// NOTE: Below assumption disallows early decoding well-known attributes with optional parameters.
if (attributeArgCount == parameterCount)
{
StringComparison options = description.MatchIgnoringCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
return attributeType.Name.Equals(description.Name, options) && namespaceMatch(attributeType.ContainingNamespace, description.Namespace, options);
}
}
return false;
static bool namespaceMatch(INamespaceSymbolInternal container, string namespaceName, StringComparison options)
{
int index = namespaceName.Length;
bool expectDot = false;
while (true)
{
if (container.IsGlobalNamespace)
{
return index == 0;
}
if (expectDot)
{
index--;
if (index < 0 || namespaceName[index] != '.')
{
return false;
}
}
else
{
expectDot = true;
}
string name = container.Name;
int nameLength = name.Length;
index -= nameLength;
if (index < 0 || string.Compare(namespaceName, index, name, 0, nameLength, options) != 0)
{
return false;
}
container = container.ContainingNamespace;
if (container is null)
{
return false;
}
}
}
}
/// <summary>
/// Returns the value of a constructor argument as type <typeparamref name="T"/>.
/// Throws if no constructor argument exists or the argument cannot be converted to the type.
/// </summary>
internal T? GetConstructorArgument<T>(int i, SpecialType specialType)
{
var constructorArgs = this.CommonConstructorArguments;
return constructorArgs[i].DecodeValue<T>(specialType);
}
/// <summary>
/// Returns named attribute argument with the given <paramref name="name"/> as type <typeparamref name="T"/>.
/// If there is more than one named argument with this name, it returns the last one.
/// If no named argument is found then the <paramref name="defaultValue"/> is returned.
/// </summary>
/// <param name="name">The metadata property or field name. This name is case sensitive (both VB and C#).</param>
/// <param name="specialType">SpecialType of the named argument.</param>
/// <param name="defaultValue">Default value for the named argument.</param>
/// <remarks>
/// For user defined attributes VB allows duplicate named arguments and uses the last value.
/// Dev11 reports an error for pseudo-custom attributes when emitting metadata. We don't.
/// </remarks>
internal T? DecodeNamedArgument<T>(string name, SpecialType specialType, T? defaultValue = default)
{
return DecodeNamedArgument<T>(CommonNamedArguments, name, specialType, defaultValue);
}
private static T? DecodeNamedArgument<T>(ImmutableArray<KeyValuePair<string, TypedConstant>> namedArguments, string name, SpecialType specialType, T? defaultValue = default)
{
int index = IndexOfNamedArgument(namedArguments, name);
return index >= 0 ? namedArguments[index].Value.DecodeValue<T>(specialType) : defaultValue;
}
private static int IndexOfNamedArgument(ImmutableArray<KeyValuePair<string, TypedConstant>> namedArguments, string name)
{
// For user defined attributes VB allows duplicate named arguments and uses the last value.
// Dev11 reports an error for pseudo-custom attributes when emitting metadata. We don't.
for (int i = namedArguments.Length - 1; i >= 0; i--)
{
// even for VB this is case sensitive comparison:
if (string.Equals(namedArguments[i].Key, name, StringComparison.Ordinal))
{
return i;
}
}
return -1;
}
#region Decimal and DateTime Constant Decoding
internal ConstantValue DecodeDecimalConstantValue()
{
// There are two decimal constant attribute ctors:
// (byte scale, byte sign, uint high, uint mid, uint low) and
// (byte scale, byte sign, int high, int mid, int low)
// The dev10 compiler only honors the first; Roslyn honours both.
// We should not end up in this code path unless we know we have one of them.
Debug.Assert(AttributeConstructor is object);
var parameters = AttributeConstructor.Parameters;
ImmutableArray<TypedConstant> args = this.CommonConstructorArguments;
Debug.Assert(parameters.Length == 5);
Debug.Assert(parameters[0].Type.SpecialType == SpecialType.System_Byte);
Debug.Assert(parameters[1].Type.SpecialType == SpecialType.System_Byte);
int low, mid, high;
byte scale = args[0].DecodeValue<byte>(SpecialType.System_Byte);
bool isNegative = args[1].DecodeValue<byte>(SpecialType.System_Byte) != 0;
if (parameters[2].Type.SpecialType == SpecialType.System_Int32)
{
Debug.Assert(parameters[2].Type.SpecialType == SpecialType.System_Int32);
Debug.Assert(parameters[3].Type.SpecialType == SpecialType.System_Int32);
Debug.Assert(parameters[4].Type.SpecialType == SpecialType.System_Int32);
high = args[2].DecodeValue<int>(SpecialType.System_Int32);
mid = args[3].DecodeValue<int>(SpecialType.System_Int32);
low = args[4].DecodeValue<int>(SpecialType.System_Int32);
}
else
{
Debug.Assert(parameters[2].Type.SpecialType == SpecialType.System_UInt32);
Debug.Assert(parameters[3].Type.SpecialType == SpecialType.System_UInt32);
Debug.Assert(parameters[4].Type.SpecialType == SpecialType.System_UInt32);
high = unchecked((int)args[2].DecodeValue<uint>(SpecialType.System_UInt32));
mid = unchecked((int)args[3].DecodeValue<uint>(SpecialType.System_UInt32));
low = unchecked((int)args[4].DecodeValue<uint>(SpecialType.System_UInt32));
}
return ConstantValue.Create(new decimal(low, mid, high, isNegative, scale));
}
internal ConstantValue DecodeDateTimeConstantValue()
{
long value = this.CommonConstructorArguments[0].DecodeValue<long>(SpecialType.System_Int64);
// if value is outside this range, DateTime would throw when constructed
if (value < DateTime.MinValue.Ticks || value > DateTime.MaxValue.Ticks)
{
return ConstantValue.Bad;
}
return ConstantValue.Create(new DateTime(value));
}
#endregion
internal ObsoleteAttributeData DecodeObsoleteAttribute(ObsoleteAttributeKind kind)
{
switch (kind)
{
case ObsoleteAttributeKind.Obsolete:
return DecodeObsoleteAttribute();
case ObsoleteAttributeKind.Deprecated:
return DecodeDeprecatedAttribute();
case ObsoleteAttributeKind.WindowsExperimental:
return DecodeWindowsExperimentalAttribute();
case ObsoleteAttributeKind.Experimental:
return DecodeExperimentalAttribute();
default:
throw ExceptionUtilities.UnexpectedValue(kind);
}
}
internal ObsoleteAttributeData DecodeExperimentalAttribute()
{
// ExperimentalAttribute(string diagnosticId)
Debug.Assert(this.CommonConstructorArguments.Length == 1);
string? diagnosticId = this.CommonConstructorArguments[0].ValueInternal as string;
if (string.IsNullOrWhiteSpace(diagnosticId))
{
diagnosticId = null;
}
string? urlFormat = null;
string? message = null;
foreach (var (name, value) in this.CommonNamedArguments)
{
if (urlFormat is null && name == ObsoleteAttributeData.UrlFormatPropertyName && IsStringProperty(ObsoleteAttributeData.UrlFormatPropertyName))
{
urlFormat = value.ValueInternal as string;
}
if (message is null && name == ObsoleteAttributeData.MessagePropertyName && IsStringProperty(ObsoleteAttributeData.MessagePropertyName))
{
message = value.ValueInternal as string;
}
if (urlFormat is not null && message is not null)
{
break;
}
}
return new ObsoleteAttributeData(ObsoleteAttributeKind.Experimental, message: message, isError: false, diagnosticId, urlFormat);
}
/// <summary>
/// Decode the arguments to ObsoleteAttribute. ObsoleteAttribute can have 0, 1 or 2 arguments.
/// </summary>
private ObsoleteAttributeData DecodeObsoleteAttribute()
{
ImmutableArray<TypedConstant> args = this.CommonConstructorArguments;
// ObsoleteAttribute()
string? message = null;
bool isError = false;
if (args.Length > 0)
{
// ObsoleteAttribute(string)
// ObsoleteAttribute(string, bool)
Debug.Assert(args.Length <= 2);
message = (string?)args[0].ValueInternal;
if (args.Length == 2)
{
Debug.Assert(args[1].ValueInternal is object);
isError = (bool)args[1].ValueInternal!;
}
}
string? diagnosticId = null;
string? urlFormat = null;
foreach (var (name, value) in this.CommonNamedArguments)
{
if (diagnosticId is null && name == ObsoleteAttributeData.DiagnosticIdPropertyName && IsStringProperty(ObsoleteAttributeData.DiagnosticIdPropertyName))
{
diagnosticId = value.ValueInternal as string;
}
else if (urlFormat is null && name == ObsoleteAttributeData.UrlFormatPropertyName && IsStringProperty(ObsoleteAttributeData.UrlFormatPropertyName))
{
urlFormat = value.ValueInternal as string;
}
if (diagnosticId is object && urlFormat is object)
{
break;
}
}
return new ObsoleteAttributeData(ObsoleteAttributeKind.Obsolete, message, isError, diagnosticId, urlFormat);
}
// Note: it is disallowed to declare a property and a field
// with the same name in C# or VB source, even if it is allowed in IL.
//
// We use a virtual method and override to prevent having to realize the public symbols just to decode obsolete attributes.
// Ideally we would use an abstract method, but that would require making the method visible to
// public consumers who inherit from this class, which we don't want to do.
// Therefore we just make it a 'private protected virtual' method instead.
private protected virtual bool IsStringProperty(string memberName) => throw ExceptionUtilities.Unreachable();
/// <summary>
/// Decode the arguments to DeprecatedAttribute. DeprecatedAttribute can have 3 or 4 arguments.
/// </summary>
private ObsoleteAttributeData DecodeDeprecatedAttribute()
{
var args = this.CommonConstructorArguments;
// DeprecatedAttribute()
string? message = null;
bool isError = false;
if (args.Length == 3 || args.Length == 4)
{
// DeprecatedAttribute(String, DeprecationType, UInt32)
// DeprecatedAttribute(String, DeprecationType, UInt32, Platform)
// DeprecatedAttribute(String, DeprecationType, UInt32, String)
Debug.Assert(args[1].ValueInternal is object);
message = (string?)args[0].ValueInternal;
isError = ((int)args[1].ValueInternal! == 1);
}
return new ObsoleteAttributeData(ObsoleteAttributeKind.Deprecated, message, isError, diagnosticId: null, urlFormat: null);
}
/// <summary>
/// Decode the arguments to ExperimentalAttribute. ExperimentalAttribute has 0 arguments.
/// </summary>
private ObsoleteAttributeData DecodeWindowsExperimentalAttribute()
{
// ExperimentalAttribute()
Debug.Assert(this.CommonConstructorArguments.Length == 0);
return ObsoleteAttributeData.WindowsExperimental;
}
internal static void DecodeMethodImplAttribute<T, TAttributeSyntaxNode, TAttributeData, TAttributeLocation>(
ref DecodeWellKnownAttributeArguments<TAttributeSyntaxNode, TAttributeData, TAttributeLocation> arguments,
CommonMessageProvider messageProvider)
where T : CommonMethodWellKnownAttributeData, new()
where TAttributeSyntaxNode : SyntaxNode
where TAttributeData : AttributeData
{
Debug.Assert(arguments.AttributeSyntaxOpt is object);
MethodImplOptions options;
var attribute = arguments.Attribute;
if (attribute.CommonConstructorArguments.Length == 1)
{
Debug.Assert(attribute.AttributeConstructor is object);
if (attribute.AttributeConstructor.Parameters[0].Type.SpecialType == SpecialType.System_Int16)
{
options = (MethodImplOptions)attribute.CommonConstructorArguments[0].DecodeValue<short>(SpecialType.System_Int16);
}
else
{
options = attribute.CommonConstructorArguments[0].DecodeValue<MethodImplOptions>(SpecialType.System_Enum);
}
// low two bits should only be set via MethodCodeType property
if (((int)options & 3) != 0)
{
messageProvider.ReportInvalidAttributeArgument(arguments.Diagnostics, arguments.AttributeSyntaxOpt, 0, attribute);
options = options & ~(MethodImplOptions)3;
}
}
else
{
options = default;
}
MethodImplAttributes codeType = MethodImplAttributes.IL;
int position = attribute.CommonConstructorArguments.Length;
foreach (var namedArg in attribute.CommonNamedArguments)
{
if (namedArg.Key == "MethodCodeType")
{
var value = (MethodImplAttributes)namedArg.Value.DecodeValue<int>(SpecialType.System_Enum);
if (value < 0 || value > MethodImplAttributes.Runtime)
{
Debug.Assert(attribute.AttributeClass is object);
messageProvider.ReportInvalidNamedArgument(arguments.Diagnostics, arguments.AttributeSyntaxOpt, position, attribute.AttributeClass, "MethodCodeType");
}
else
{
codeType = value;
}
}
position++;
}
arguments.GetOrCreateData<T>().SetMethodImplementation(arguments.Index, (MethodImplAttributes)options | codeType);
}
internal static void DecodeStructLayoutAttribute<TTypeWellKnownAttributeData, TAttributeSyntaxNode, TAttributeData, TAttributeLocation>(
ref DecodeWellKnownAttributeArguments<TAttributeSyntaxNode, TAttributeData, TAttributeLocation> arguments,
CharSet defaultCharSet,
int defaultAutoLayoutSize,
CommonMessageProvider messageProvider)
where TTypeWellKnownAttributeData : CommonTypeWellKnownAttributeData, new()
where TAttributeSyntaxNode : SyntaxNode
where TAttributeData : AttributeData
{
Debug.Assert(arguments.AttributeSyntaxOpt is object);
var attribute = arguments.Attribute;
Debug.Assert(attribute.AttributeClass is object);
CharSet charSet = (defaultCharSet != Cci.Constants.CharSet_None) ? defaultCharSet : CharSet.Ansi;
int? size = null;
int? alignment = null;
bool hasErrors = false;
LayoutKind kind = attribute.CommonConstructorArguments[0].DecodeValue<LayoutKind>(SpecialType.System_Enum);
switch (kind)
{
case LayoutKind.Auto:
case LayoutKind.Explicit:
case LayoutKind.Sequential:
break;
default:
messageProvider.ReportInvalidAttributeArgument(arguments.Diagnostics, arguments.AttributeSyntaxOpt, 0, attribute);
hasErrors = true;
break;
}
int position = attribute.CommonConstructorArguments.Length;
foreach (var namedArg in attribute.CommonNamedArguments)
{
switch (namedArg.Key)
{
case "CharSet":
charSet = namedArg.Value.DecodeValue<CharSet>(SpecialType.System_Enum);
switch (charSet)
{
case Cci.Constants.CharSet_None:
charSet = CharSet.Ansi;
break;
case CharSet.Ansi:
case Cci.Constants.CharSet_Auto:
case CharSet.Unicode:
break;
default:
messageProvider.ReportInvalidNamedArgument(arguments.Diagnostics, arguments.AttributeSyntaxOpt, position, attribute.AttributeClass, namedArg.Key);
hasErrors = true;
break;
}
break;
case "Pack":
alignment = namedArg.Value.DecodeValue<int>(SpecialType.System_Int32);
// only powers of 2 less or equal to 128 are allowed:
if (alignment > 128 || (alignment & (alignment - 1)) != 0)
{
messageProvider.ReportInvalidNamedArgument(arguments.Diagnostics, arguments.AttributeSyntaxOpt, position, attribute.AttributeClass, namedArg.Key);
hasErrors = true;
}
break;
case "Size":
size = namedArg.Value.DecodeValue<int>(Microsoft.CodeAnalysis.SpecialType.System_Int32);
if (size < 0)
{
messageProvider.ReportInvalidNamedArgument(arguments.Diagnostics, arguments.AttributeSyntaxOpt, position, attribute.AttributeClass, namedArg.Key);
hasErrors = true;
}
break;
}
position++;
}
if (!hasErrors)
{
if (kind == LayoutKind.Auto && size == null && alignment != null)
{
// If size is unspecified
// C# emits size=0
// VB emits size=1 if the type is a struct, auto-layout and has alignment specified; 0 otherwise
size = defaultAutoLayoutSize;
}
arguments.GetOrCreateData<TTypeWellKnownAttributeData>().SetStructLayout(new TypeLayout(kind, size ?? 0, (byte)(alignment ?? 0)), charSet);
}
}
internal AttributeUsageInfo DecodeAttributeUsageAttribute()
{
return DecodeAttributeUsageAttribute(this.CommonConstructorArguments[0], this.CommonNamedArguments);
}
internal static AttributeUsageInfo DecodeAttributeUsageAttribute(TypedConstant positionalArg, ImmutableArray<KeyValuePair<string, TypedConstant>> namedArgs)
{
// BREAKING CHANGE (C#):
// If the well known attribute class System.AttributeUsage is overridden in source,
// we will use the overriding AttributeUsage type for attribute usage validation,
// i.e. we try to find a constructor in that type with signature AttributeUsage(AttributeTargets)
// and public bool properties Inherited and AllowMultiple.
// If we are unable to find any of these, we use their default values.
//
// This behavior matches the approach chosen by native VB and Roslyn VB compilers,
// but breaks compatibility with native C# compiler.
// Native C# compiler preloads all the well known attribute types from mscorlib prior to binding,
// hence it uses the AttributeUsage type defined in mscorlib for attribute usage validation.
//
// See Roslyn Bug 8603: ETA crashes with InvalidOperationException on duplicate attributes for details.
Debug.Assert(positionalArg.ValueInternal is object);
var validOn = (AttributeTargets)positionalArg.ValueInternal;
bool allowMultiple = DecodeNamedArgument(namedArgs, "AllowMultiple", SpecialType.System_Boolean, false);
bool inherited = DecodeNamedArgument(namedArgs, "Inherited", SpecialType.System_Boolean, true);
return new AttributeUsageInfo(validOn, allowMultiple, inherited);
}
}
}
|