// 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.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reflection.Runtime.BindingFlagSupport;
using System.Reflection.Runtime.General;
namespace System.Reflection.Runtime.TypeInfos
{
internal abstract partial class RuntimeTypeInfo
{
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2072:UnrecognizedReflectionPattern",
Justification = "Analysis does not track annotations for RuntimeTypeInfo")]
public object? InvokeMember(
string name, BindingFlags bindingFlags, Binder? binder, object? target,
object?[]? providedArgs, ParameterModifier[]? modifiers, CultureInfo? culture, string[]? namedParams)
{
const BindingFlags MemberBindingMask = (BindingFlags)0x000000FF;
const BindingFlags InvocationMask = (BindingFlags)0x0000FF00;
const BindingFlags BinderGetSetProperty = BindingFlags.GetProperty | BindingFlags.SetProperty;
const BindingFlags BinderGetSetField = BindingFlags.GetField | BindingFlags.SetField;
const BindingFlags BinderNonFieldGetSet = (BindingFlags)0x00FFF300;
const BindingFlags BinderNonCreateInstance = BindingFlags.InvokeMethod | BinderGetSetField | BinderGetSetProperty;
if (IsGenericParameter)
throw new InvalidOperationException(SR.Arg_GenericParameter);
#region Preconditions
if ((bindingFlags & InvocationMask) == 0)
// "Must specify binding flags describing the invoke operation required."
throw new ArgumentException(SR.Arg_NoAccessSpec, nameof(bindingFlags));
// Provide a default binding mask if none is provided
if ((bindingFlags & MemberBindingMask) == 0)
{
bindingFlags |= BindingFlags.Instance | BindingFlags.Public;
if ((bindingFlags & BindingFlags.CreateInstance) == 0)
bindingFlags |= BindingFlags.Static;
}
// There must not be more named parameters than provided arguments
if (namedParams != null)
{
if (providedArgs != null)
{
if (namedParams.Length > providedArgs.Length)
// "Named parameter array can not be bigger than argument array."
throw new ArgumentException(SR.Arg_NamedParamTooBig, nameof(namedParams));
}
else
{
if (namedParams.Length != 0)
// "Named parameter array can not be bigger than argument array."
throw new ArgumentException(SR.Arg_NamedParamTooBig, nameof(namedParams));
}
}
#endregion
#region COM Interop
if (target != null && target.GetType().IsCOMObject)
throw new PlatformNotSupportedException(SR.PlatformNotSupported_ComInterop);
#endregion
#region Check that any named parameters are not null
if (namedParams != null && Array.IndexOf(namedParams, null) >= 0)
// "Named parameter value must not be null."
throw new ArgumentException(SR.Arg_NamedParamNull, nameof(namedParams));
#endregion
int argCnt = (providedArgs != null) ? providedArgs.Length : 0;
#region Get a Binder
binder ??= Type.DefaultBinder;
#endregion
#region Delegate to Activator.CreateInstance
if ((bindingFlags & BindingFlags.CreateInstance) != 0)
{
if ((bindingFlags & BindingFlags.CreateInstance) != 0 && (bindingFlags & BinderNonCreateInstance) != 0)
// "Can not specify both CreateInstance and another access type."
throw new ArgumentException(SR.Arg_CreatInstAccess, nameof(bindingFlags));
return Activator.CreateInstance(this.ToType(), bindingFlags, binder, providedArgs, culture);
}
#endregion
// PutDispProperty and\or PutRefDispProperty ==> SetProperty.
if ((bindingFlags & (BindingFlags.PutDispProperty | BindingFlags.PutRefDispProperty)) != 0)
bindingFlags |= BindingFlags.SetProperty;
#region Name
ArgumentNullException.ThrowIfNull(name);
if (name.Length == 0 || name.Equals(@"[DISPID=0]"))
{
// in InvokeMember we always pretend there is a default member if none is provided and we make it ToString
name = GetDefaultMemberName() ?? "ToString";
}
#endregion
#region GetField or SetField
bool IsGetField = (bindingFlags & BindingFlags.GetField) != 0;
bool IsSetField = (bindingFlags & BindingFlags.SetField) != 0;
if (IsGetField || IsSetField)
{
#region Preconditions
if (IsGetField)
{
if (IsSetField)
// "Can not specify both Get and Set on a field."
throw new ArgumentException(SR.Arg_FldSetGet, nameof(bindingFlags));
if ((bindingFlags & BindingFlags.SetProperty) != 0)
// "Can not specify both GetField and SetProperty."
throw new ArgumentException(SR.Arg_FldGetPropSet, nameof(bindingFlags));
}
else
{
Debug.Assert(IsSetField);
ArgumentNullException.ThrowIfNull(providedArgs);
if ((bindingFlags & BindingFlags.GetProperty) != 0)
// "Can not specify both SetField and GetProperty."
throw new ArgumentException(SR.Arg_FldSetPropGet, nameof(bindingFlags));
if ((bindingFlags & BindingFlags.InvokeMethod) != 0)
// "Can not specify Set on a Field and Invoke on a method."
throw new ArgumentException(SR.Arg_FldSetInvoke, nameof(bindingFlags));
}
#endregion
#region Lookup Field
FieldInfo? selFld = null;
FieldInfo[] flds = (FieldInfo[])GetMember(name, MemberTypes.Field, bindingFlags);
if (flds.Length == 1)
{
selFld = flds[0];
}
else if (flds.Length > 0)
{
selFld = binder.BindToField(bindingFlags, flds, IsGetField ? Empty.Value : providedArgs[0], culture);
}
#endregion
if (selFld != null)
{
#region Invocation on a field
if (selFld.FieldType.IsArray || object.ReferenceEquals(selFld.FieldType, typeof(Array)))
{
#region Invocation of an array Field
int idxCnt;
if ((bindingFlags & BindingFlags.GetField) != 0)
{
idxCnt = argCnt;
}
else
{
idxCnt = argCnt - 1;
}
if (idxCnt > 0)
{
Debug.Assert(providedArgs != null);
// Verify that all of the index values are ints
int[] idx = new int[idxCnt];
for (int i = 0; i < idxCnt; i++)
{
try
{
idx[i] = ((IConvertible)providedArgs[i]!).ToInt32(null);
}
catch (InvalidCastException)
{
throw new ArgumentException(SR.Arg_IndexMustBeInt);
}
}
// Set or get the value...
Array a = (Array)selFld.GetValue(target)!;
// Set or get the value in the array
if ((bindingFlags & BindingFlags.GetField) != 0)
{
return a.GetValue(idx);
}
else
{
a.SetValue(providedArgs[idxCnt], idx);
return null;
}
}
#endregion
}
if (IsGetField)
{
#region Get the field value
if (argCnt != 0)
throw new ArgumentException(SR.Arg_FldGetArgErr, nameof(bindingFlags));
return selFld.GetValue(target);
#endregion
}
else
{
#region Set the field Value
if (argCnt != 1)
throw new ArgumentException(SR.Arg_FldSetArgErr, nameof(bindingFlags));
selFld.SetValue(target, providedArgs[0], bindingFlags, binder, culture);
return null;
#endregion
}
#endregion
}
if ((bindingFlags & BinderNonFieldGetSet) == 0)
throw new MissingFieldException(FullName, name);
}
#endregion
#region Property PreConditions
// @Legacy - This is RTM behavior
bool isGetProperty = (bindingFlags & BindingFlags.GetProperty) != 0;
bool isSetProperty = (bindingFlags & BindingFlags.SetProperty) != 0;
if (isGetProperty || isSetProperty)
{
#region Preconditions
if (isGetProperty)
{
Debug.Assert(!IsSetField);
if (isSetProperty)
throw new ArgumentException(SR.Arg_PropSetGet, nameof(bindingFlags));
}
else
{
Debug.Assert(isSetProperty);
Debug.Assert(!IsGetField);
if ((bindingFlags & BindingFlags.InvokeMethod) != 0)
throw new ArgumentException(SR.Arg_PropSetInvoke, nameof(bindingFlags));
}
#endregion
}
#endregion
MethodInfo[]? finalists = null;
MethodInfo? finalist = null;
#region BindingFlags.InvokeMethod
if ((bindingFlags & BindingFlags.InvokeMethod) != 0)
{
#region Lookup Methods
MethodInfo[] semiFinalists = (MethodInfo[])GetMember(name, MemberTypes.Method, bindingFlags);
ArrayBuilder<MethodInfo> results = default;
for (int i = 0; i < semiFinalists.Length; i++)
{
MethodInfo semiFinalist = semiFinalists[i];
Debug.Assert(semiFinalist != null);
if (!semiFinalist.QualifiesBasedOnParameterCount(bindingFlags, CallingConventions.Any, new Type[argCnt]))
continue;
if (finalist == null)
{
finalist = semiFinalist;
}
else
{
if (results.Count == 0)
{
results.Add(finalist);
}
results.Add(semiFinalist);
}
}
if (results.Count > 0)
{
finalists = results.ToArray();
}
#endregion
}
#endregion
Debug.Assert(finalists == null || finalist != null);
#region BindingFlags.GetProperty or BindingFlags.SetProperty
if (finalist == null && isGetProperty || isSetProperty)
{
#region Lookup Property
PropertyInfo[] semiFinalists = (PropertyInfo[])GetMember(name, MemberTypes.Property, bindingFlags);
ArrayBuilder<MethodInfo> results = default;
for (int i = 0; i < semiFinalists.Length; i++)
{
MethodInfo? semiFinalist;
if (isSetProperty)
{
semiFinalist = semiFinalists[i].GetSetMethod(true);
}
else
{
semiFinalist = semiFinalists[i].GetGetMethod(true);
}
if (semiFinalist == null)
continue;
BindingFlags expectedBindingFlags = semiFinalist.IsPublic ? BindingFlags.Public : BindingFlags.NonPublic;
if ((bindingFlags & expectedBindingFlags) != expectedBindingFlags)
continue;
if (!semiFinalist.QualifiesBasedOnParameterCount(bindingFlags, CallingConventions.Any, new Type[argCnt]))
continue;
if (finalist == null)
{
finalist = semiFinalist;
}
else
{
if (results.Count == 0)
{
results.Add(finalist);
}
results.Add(semiFinalist);
}
}
if (results.Count > 0)
{
finalists = results.ToArray();
}
#endregion
}
#endregion
if (finalist != null)
{
#region Invoke
if (finalists == null &&
argCnt == 0 &&
finalist.GetParametersAsSpan().Length == 0 &&
(bindingFlags & BindingFlags.OptionalParamBinding) == 0)
{
//if (useCache && argCnt == props[0].GetParameters().Length)
// AddMethodToCache(name, bindingFlags, argCnt, providedArgs, props[0]);
return finalist.Invoke(target, bindingFlags, binder, providedArgs, culture);
}
finalists ??= new MethodInfo[] { finalist };
providedArgs ??= Array.Empty<object>();
object? state = null;
MethodBase? invokeMethod = null;
try { invokeMethod = binder.BindToMethod(bindingFlags, finalists, ref providedArgs, modifiers, culture, namedParams, out state); }
catch (MissingMethodException) { }
if (invokeMethod == null)
throw new MissingMethodException(FullName, name);
//if (useCache && argCnt == invokeMethod.GetParameters().Length)
// AddMethodToCache(name, bindingFlags, argCnt, providedArgs, invokeMethod);
object? result = ((MethodInfo)invokeMethod).Invoke(target, bindingFlags, binder, providedArgs, culture);
if (state != null)
binder.ReorderArgumentArray(ref providedArgs, state);
return result;
#endregion
}
throw new MissingMethodException(FullName, name);
}
}
}