|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using MdToken = System.Reflection.MetadataToken;
namespace System.Reflection
{
internal sealed unsafe class RuntimeParameterInfo : ParameterInfo
{
#region Static Members
internal static ParameterInfo[] GetParameters(IRuntimeMethodInfo method, MemberInfo member, Signature sig)
{
Debug.Assert(method is RuntimeMethodInfo || method is RuntimeConstructorInfo);
return GetParameters(method, member, sig, out _, fetchReturnParameter: false);
}
internal static ParameterInfo GetReturnParameter(IRuntimeMethodInfo method, MemberInfo member, Signature sig)
{
Debug.Assert(method is RuntimeMethodInfo || method is RuntimeConstructorInfo);
GetParameters(method, member, sig, out ParameterInfo? returnParameter, fetchReturnParameter: true);
return returnParameter!;
}
private static ParameterInfo[] GetParameters(
IRuntimeMethodInfo methodHandle, MemberInfo member, Signature sig, out ParameterInfo? returnParameter, bool fetchReturnParameter)
{
// The lifetime rules for MetadataImport expect these two objects to be the same instance.
// See the lifetime of MetadataImport, acquired through IRuntimeMethodInfo, but extended
// through the MemberInfo instance.
Debug.Assert(ReferenceEquals(methodHandle, member));
returnParameter = null;
int sigArgCount = sig.Arguments.Length;
ParameterInfo[] args =
fetchReturnParameter ? null! :
sigArgCount == 0 ? Array.Empty<ParameterInfo>() :
new ParameterInfo[sigArgCount];
int tkMethodDef = RuntimeMethodHandle.GetMethodDef(methodHandle);
int cParamDefs = 0;
// Not all methods have tokens. Arrays, pointers and byRef types do not have tokens as they
// are generated on the fly by the runtime.
if (!MdToken.IsNullToken(tkMethodDef))
{
MetadataImport scope = RuntimeMethodHandle.GetDeclaringType(methodHandle).GetRuntimeModule().MetadataImport;
scope.EnumParams(tkMethodDef, out MetadataEnumResult tkParamDefs);
cParamDefs = tkParamDefs.Length;
// Not all parameters have tokens. Parameters may have no token
// if they have no name and no attributes.
if (cParamDefs > sigArgCount + 1 /* return type */)
throw new BadImageFormatException(SR.BadImageFormat_ParameterSignatureMismatch);
for (int i = 0; i < cParamDefs; i++)
{
#region Populate ParameterInfos
int tkParamDef = tkParamDefs[i];
scope.GetParamDefProps(tkParamDef, out int position, out ParameterAttributes attr);
position--;
if (fetchReturnParameter && position == -1)
{
// more than one return parameter?
if (returnParameter != null)
throw new BadImageFormatException(SR.BadImageFormat_ParameterSignatureMismatch);
returnParameter = new RuntimeParameterInfo(sig, scope, tkParamDef, position, attr, member);
}
else if (!fetchReturnParameter && position >= 0)
{
// position beyond sigArgCount?
if (position >= sigArgCount)
throw new BadImageFormatException(SR.BadImageFormat_ParameterSignatureMismatch);
args[position] = new RuntimeParameterInfo(sig, scope, tkParamDef, position, attr, member);
}
#endregion
}
}
// Fill in empty ParameterInfos for those without tokens
if (fetchReturnParameter)
{
returnParameter ??= new RuntimeParameterInfo(sig, default, 0, -1, (ParameterAttributes)0, member);
}
else
{
if (cParamDefs < args.Length + 1)
{
for (int i = 0; i < args.Length; i++)
{
if (args[i] != null)
continue;
args[i] = new RuntimeParameterInfo(sig, default, 0, i, (ParameterAttributes)0, member);
}
}
}
return args;
}
#endregion
#region Private Data Members
private readonly int m_tkParamDef;
private readonly MetadataImport m_scope;
private readonly Signature? m_signature;
private volatile bool m_nameIsCached;
private readonly bool m_noMetadata;
private bool m_noDefaultValue;
private readonly MethodBase? m_originalMember;
#endregion
#region Internal Properties
internal MethodBase DefiningMethod
{
get
{
MethodBase? result = m_originalMember ?? MemberImpl as MethodBase;
Debug.Assert(result != null);
return result;
}
}
#endregion
#region Internal Methods
internal void SetName(string? name)
{
NameImpl = name;
}
internal void SetAttributes(ParameterAttributes attributes)
{
AttrsImpl = attributes;
}
#endregion
#region Constructor
// used by RuntimePropertyInfo
internal RuntimeParameterInfo(RuntimeParameterInfo accessor, RuntimePropertyInfo property)
: this(accessor, (MemberInfo)property)
{
m_signature = property.Signature;
}
private RuntimeParameterInfo(RuntimeParameterInfo accessor, MemberInfo member)
{
// Change ownership
MemberImpl = member;
// The original owner should always be a method, because this method is only used to
// change the owner from a method to a property.
m_originalMember = accessor.MemberImpl as MethodBase;
Debug.Assert(m_originalMember != null);
// Populate all the caches -- we inherit this behavior from RTM
NameImpl = accessor.Name;
m_nameIsCached = true;
ClassImpl = accessor.ParameterType;
PositionImpl = accessor.Position;
AttrsImpl = accessor.Attributes;
// Strictly speaking, properties don't contain parameter tokens
// However we need this to make ca's work... oh well...
m_tkParamDef = MdToken.IsNullToken(accessor.MetadataToken) ? (int)MetadataTokenType.ParamDef : accessor.MetadataToken;
m_scope = accessor.m_scope;
}
private RuntimeParameterInfo(
Signature signature, MetadataImport scope, int tkParamDef,
int position, ParameterAttributes attributes, MemberInfo member)
{
Debug.Assert(member != null);
Debug.Assert(MdToken.IsNullToken(tkParamDef) == scope.Equals((MetadataImport)default));
Debug.Assert(MdToken.IsNullToken(tkParamDef) || MdToken.IsTokenOfType(tkParamDef, MetadataTokenType.ParamDef));
PositionImpl = position;
MemberImpl = member;
m_signature = signature;
m_tkParamDef = MdToken.IsNullToken(tkParamDef) ? (int)MetadataTokenType.ParamDef : tkParamDef;
m_scope = scope;
AttrsImpl = attributes;
ClassImpl = null;
NameImpl = null;
}
// ctor for no metadata MethodInfo in the DynamicMethod and RuntimeMethodInfo cases
internal RuntimeParameterInfo(MethodInfo owner, string? name, Type parameterType, int position)
{
MemberImpl = owner;
NameImpl = name;
m_nameIsCached = true;
m_noMetadata = true;
ClassImpl = parameterType;
PositionImpl = position;
AttrsImpl = ParameterAttributes.None;
m_tkParamDef = (int)MetadataTokenType.ParamDef;
m_scope = default;
}
#endregion
#region Public Methods
public override Type ParameterType
{
get
{
// only instance of ParameterInfo has ClassImpl, all its subclasses don't
if (ClassImpl == null)
{
Debug.Assert(m_signature != null);
RuntimeType parameterType;
if (PositionImpl == -1)
parameterType = m_signature.ReturnType;
else
parameterType = m_signature.Arguments[PositionImpl];
Debug.Assert(parameterType != null);
// different thread could only write ClassImpl to the same value, so a race condition is not a problem here
ClassImpl = parameterType;
}
return ClassImpl;
}
}
public override string? Name
{
get
{
if (!m_nameIsCached)
{
if (!MdToken.IsNullToken(m_tkParamDef))
{
string name = m_scope.GetName(m_tkParamDef).ToString();
GC.KeepAlive(this);
NameImpl = name;
}
// other threads could only write it to true, so a race condition is OK
// this field is volatile, so the write ordering is guaranteed
m_nameIsCached = true;
}
// name may be null
return NameImpl;
}
}
public override bool HasDefaultValue
{
get
{
if (m_noMetadata || m_noDefaultValue)
return false;
return TryGetDefaultValueInternal(false, out _);
}
}
public override object? DefaultValue => GetDefaultValue(false);
public override object? RawDefaultValue => GetDefaultValue(true);
private object? GetDefaultValue(bool raw)
{
// OLD COMMENT (Is this even true?)
// Cannot cache because default value could be non-agile user defined enumeration.
// OLD COMMENT ends
if (m_noMetadata)
return null;
// for dynamic method we pretend to have cached the value so we do not go to metadata
if (!TryGetDefaultValueInternal(raw, out object? defaultValue))
{
#region Handle case if no default value was found
if (IsOptional)
{
// If the argument is marked as optional then the default value is Missing.Value.
defaultValue = Type.Missing;
}
#endregion
}
return defaultValue;
}
private object? GetDefaultValueFromCustomAttributeData()
{
foreach (CustomAttributeData attributeData in CustomAttributeData.GetCustomAttributes(this))
{
Type attributeType = attributeData.AttributeType;
if (attributeType == typeof(DecimalConstantAttribute))
{
return GetRawDecimalConstant(attributeData);
}
else if (attributeType.IsSubclassOf(typeof(CustomConstantAttribute)))
{
if (attributeType == typeof(DateTimeConstantAttribute))
{
return GetRawDateTimeConstant(attributeData);
}
return GetRawConstant(attributeData);
}
}
return DBNull.Value;
}
private object? GetDefaultValueFromCustomAttributes()
{
object[] customAttributes = GetCustomAttributes(typeof(CustomConstantAttribute), false);
if (customAttributes.Length != 0)
return ((CustomConstantAttribute)customAttributes[0]).Value;
customAttributes = GetCustomAttributes(typeof(DecimalConstantAttribute), false);
if (customAttributes.Length != 0)
return ((DecimalConstantAttribute)customAttributes[0]).Value;
return DBNull.Value;
}
// returns DBNull.Value if the parameter doesn't have a default value
private bool TryGetDefaultValueInternal(bool raw, out object? defaultValue)
{
Debug.Assert(!m_noMetadata);
if (m_noDefaultValue || MdToken.IsNullToken(m_tkParamDef))
{
defaultValue = DBNull.Value;
m_noDefaultValue = true;
return false;
}
// Prioritize metadata constant over custom attribute constant
#region Look for a default value in metadata
// This will return DBNull.Value if no constant value is defined on m_tkParamDef in the metadata.
defaultValue = MdConstant.GetValue(m_scope, m_tkParamDef, ParameterType.TypeHandle, raw);
GC.KeepAlive(this);
// If default value is not specified in metadata, look for it in custom attributes
if (defaultValue == DBNull.Value)
{
// The resolution of default value is done by following these rules:
// 1. For RawDefaultValue, we pick the first custom attribute holding the constant value
// in the following order: DecimalConstantAttribute, DateTimeConstantAttribute, CustomConstantAttribute
// 2. For DefaultValue, we first look for CustomConstantAttribute and pick the first occurrence.
// If none is found, then we repeat the same process searching for DecimalConstantAttribute.
// IMPORTANT: Please note that there is a subtle difference in order custom attributes are inspected for
// RawDefaultValue and DefaultValue.
defaultValue = raw ? GetDefaultValueFromCustomAttributeData() : GetDefaultValueFromCustomAttributes();
if (defaultValue == DBNull.Value)
{
m_noDefaultValue = true;
return false;
}
}
return true;
#endregion
}
private static decimal GetRawDecimalConstant(CustomAttributeData attr)
{
Debug.Assert(attr.Constructor.DeclaringType == typeof(DecimalConstantAttribute));
IList<CustomAttributeTypedArgument> args = attr.ConstructorArguments;
Debug.Assert(args.Count == 5);
return new decimal(
lo: GetConstructorArgument(args, 4),
mid: GetConstructorArgument(args, 3),
hi: GetConstructorArgument(args, 2),
isNegative: ((byte)args[1].Value!) != 0,
scale: (byte)args[0].Value!);
static int GetConstructorArgument(IList<CustomAttributeTypedArgument> args, int index)
{
// The constructor is overloaded to accept both signed and unsigned arguments
object obj = args[index].Value!;
return (obj is int value) ? value : (int)(uint)obj;
}
}
private static DateTime GetRawDateTimeConstant(CustomAttributeData attr)
{
Debug.Assert(attr.Constructor.DeclaringType == typeof(DateTimeConstantAttribute));
Debug.Assert(attr.ConstructorArguments.Count == 1);
return new DateTime((long)attr.ConstructorArguments[0].Value!);
}
private static object? GetRawConstant(CustomAttributeData attr)
{
// We are relying only on named arguments for historical reasons
foreach (CustomAttributeNamedArgument namedArgument in attr.NamedArguments)
{
if (namedArgument.MemberInfo.Name.Equals("Value"))
return namedArgument.TypedValue.Value;
}
// Return DBNull to indicate that no default value is available.
// Not to be confused with a null return which indicates a null default value.
return DBNull.Value;
}
internal RuntimeModule? GetRuntimeModule()
{
RuntimeMethodInfo? method = Member as RuntimeMethodInfo;
RuntimeConstructorInfo? constructor = Member as RuntimeConstructorInfo;
RuntimePropertyInfo? property = Member as RuntimePropertyInfo;
if (method != null)
return method.GetRuntimeModule();
else if (constructor != null)
return constructor.GetRuntimeModule();
else if (property != null)
return property.GetRuntimeModule();
else
return null;
}
public override int MetadataToken => m_tkParamDef;
public override Type[] GetRequiredCustomModifiers()
{
return m_signature is null ?
Type.EmptyTypes :
m_signature.GetCustomModifiers(PositionImpl + 1, true);
}
public override Type[] GetOptionalCustomModifiers()
{
return m_signature is null ?
Type.EmptyTypes :
m_signature.GetCustomModifiers(PositionImpl + 1, false);
}
public override Type GetModifiedParameterType() =>
ModifiedType.Create(unmodifiedType: ParameterType, m_signature, parameterIndex: PositionImpl + 1);
#endregion
#region ICustomAttributeProvider
public override object[] GetCustomAttributes(bool inherit)
{
if (MdToken.IsNullToken(m_tkParamDef))
return Array.Empty<object>();
return CustomAttribute.GetCustomAttributes(this, (typeof(object) as RuntimeType)!);
}
public override object[] GetCustomAttributes(Type attributeType, bool inherit)
{
ArgumentNullException.ThrowIfNull(attributeType);
if (attributeType.UnderlyingSystemType is not RuntimeType attributeRuntimeType)
throw new ArgumentException(SR.Arg_MustBeType, nameof(attributeType));
if (MdToken.IsNullToken(m_tkParamDef))
return CustomAttribute.CreateAttributeArrayHelper(attributeRuntimeType, 0);
return CustomAttribute.GetCustomAttributes(this, attributeRuntimeType);
}
public override bool IsDefined(Type attributeType, bool inherit)
{
ArgumentNullException.ThrowIfNull(attributeType);
if (MdToken.IsNullToken(m_tkParamDef))
return false;
if (attributeType.UnderlyingSystemType is not RuntimeType attributeRuntimeType)
throw new ArgumentException(SR.Arg_MustBeType, nameof(attributeType));
return CustomAttribute.IsDefined(this, attributeRuntimeType);
}
public override IList<CustomAttributeData> GetCustomAttributesData()
{
return RuntimeCustomAttributeData.GetCustomAttributesInternal(this);
}
#endregion
}
}
|