123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 |
- using System.Collections;
- using System.Diagnostics.CodeAnalysis;
- using System.Globalization;
- using System.Reflection;
- using Jint.Native;
- using Jint.Runtime.Descriptors;
- using Jint.Runtime.Descriptors.Specialized;
- #pragma warning disable IL2067
- namespace Jint.Runtime.Interop.Reflection;
- internal sealed class IndexerAccessor : ReflectionAccessor
- {
- private readonly object _key;
- private readonly MethodInfo? _getter;
- private readonly MethodInfo? _setter;
- private readonly MethodInfo? _containsKey;
- private IndexerAccessor(PropertyInfo indexer, MethodInfo? containsKey, object key) : base(indexer.PropertyType)
- {
- Indexer = indexer;
- FirstIndexParameter = indexer.GetIndexParameters()[0];
- _containsKey = containsKey;
- _key = key;
- _getter = indexer.GetGetMethod();
- _setter = indexer.GetSetMethod();
- }
- internal PropertyInfo Indexer { get; }
- internal ParameterInfo FirstIndexParameter { get; }
- internal static bool TryFindIndexer(
- Engine engine,
- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicProperties)] Type targetType,
- string propertyName,
- [NotNullWhen(true)] out IndexerAccessor? indexerAccessor,
- [NotNullWhen(true)] out PropertyInfo? indexer)
- {
- indexerAccessor = null;
- indexer = null;
- var paramTypeArray = new Type[1];
- // integer keys can be ambiguous as we only know string keys
- int? integerKey = null;
- if (int.TryParse(propertyName, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intKeyTemp))
- {
- integerKey = intKeyTemp;
- }
- var filter = new Func<MemberInfo, bool>(m => engine.Options.Interop.TypeResolver.Filter(engine, targetType, m));
- // default indexer wins
- var descriptor = TypeDescriptor.Get(targetType);
- if (descriptor.IntegerIndexerProperty is not null && !filter(descriptor.IntegerIndexerProperty))
- {
- indexerAccessor = ComposeIndexerFactory(engine, targetType, descriptor.IntegerIndexerProperty, paramType: typeof(int), propertyName, integerKey, paramTypeArray);
- if (indexerAccessor != null)
- {
- indexer = descriptor.IntegerIndexerProperty;
- return true;
- }
- }
- // try to find first indexer having either public getter or setter with matching argument type
- PropertyInfo? fallbackIndexer = null;
- foreach (var candidate in targetType.GetProperties())
- {
- if (!filter(candidate))
- {
- continue;
- }
- var indexParameters = candidate.GetIndexParameters();
- if (indexParameters.Length != 1)
- {
- continue;
- }
- if (candidate.GetGetMethod() != null || candidate.GetSetMethod() != null)
- {
- var paramType = indexParameters[0].ParameterType;
- indexerAccessor = ComposeIndexerFactory(engine, targetType, candidate, paramType, propertyName, integerKey, paramTypeArray);
- if (indexerAccessor != null)
- {
- if (paramType != typeof(string) || integerKey is null)
- {
- // exact match, we don't need to check for integer key
- indexer = candidate;
- return true;
- }
- if (fallbackIndexer is null)
- {
- // our fallback
- fallbackIndexer = candidate;
- }
- }
- }
- }
- if (fallbackIndexer is not null)
- {
- indexer = fallbackIndexer;
- // just to keep compiler happy, we know we have a value
- indexerAccessor ??= new IndexerAccessor(indexer, containsKey: null, key: null!);
- return true;
- }
- indexerAccessor = default;
- indexer = default;
- return false;
- }
- private static IndexerAccessor? ComposeIndexerFactory(
- Engine engine,
- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicProperties)] Type targetType,
- PropertyInfo candidate,
- Type paramType,
- string propertyName,
- int? integerKey,
- Type[] paramTypeArray)
- {
- // check for known incompatible types
- #if NET8_0_OR_GREATER
- if (typeof(System.Text.Json.Nodes.JsonNode).IsAssignableFrom(targetType)
- && (targetType != typeof(System.Text.Json.Nodes.JsonArray) || paramType != typeof(int))
- && (targetType != typeof(System.Text.Json.Nodes.JsonObject) || paramType != typeof(string)))
- {
- // we cannot access this[string] with anything else than JsonObject, otherwise itw will throw
- // we cannot access this[int] with anything else than JsonArray, otherwise itw will throw
- return null;
- }
- #endif
- object? key = null;
- // int key is quite common case
- if (paramType == typeof(int))
- {
- if (integerKey is not null)
- {
- key = integerKey;
- }
- }
- else
- {
- engine.TypeConverter.TryConvert(propertyName, paramType, CultureInfo.InvariantCulture, out key);
- }
- if (key is not null)
- {
- // the key can be converted for this indexer
- var indexerProperty = candidate;
- // get contains key method to avoid index exception being thrown in dictionaries
- paramTypeArray[0] = paramType;
- var containsKeyMethod = targetType.GetMethod(nameof(IDictionary<string, string>.ContainsKey), paramTypeArray);
- if (containsKeyMethod is null && targetType.IsAssignableFrom(typeof(IDictionary)))
- {
- paramTypeArray[0] = typeof(object);
- containsKeyMethod = targetType.GetMethod(nameof(IDictionary.Contains), paramTypeArray);
- }
- return new IndexerAccessor(indexerProperty, containsKeyMethod, key);
- }
- // the key type doesn't work for this indexer
- return null;
- }
- public override bool Readable => Indexer.CanRead;
- public override bool Writable => Indexer.CanWrite;
- protected override object? DoGetValue(object target, string memberName)
- {
- if (_getter is null)
- {
- Throw.InvalidOperationException("Indexer has no public getter.");
- return null;
- }
- object[] parameters = [_key];
- if (_containsKey != null)
- {
- if (_containsKey.Invoke(target, parameters) as bool? != true)
- {
- return JsValue.Undefined;
- }
- }
- try
- {
- return _getter.Invoke(target, parameters);
- }
- catch (TargetInvocationException tie) when (tie.InnerException is KeyNotFoundException)
- {
- return JsValue.Undefined;
- }
- }
- protected override void DoSetValue(object target, string memberName, object? value)
- {
- if (_setter is null)
- {
- Throw.InvalidOperationException("Indexer has no public setter.");
- }
- object?[] parameters = [_key, value];
- _setter.Invoke(target, parameters);
- }
- public override PropertyDescriptor CreatePropertyDescriptor(Engine engine, object target, string memberName, bool enumerable = true)
- {
- if (_containsKey != null)
- {
- if (_containsKey.Invoke(target, [_key]) as bool? != true)
- {
- return PropertyDescriptor.Undefined;
- }
- }
- return new ReflectionDescriptor(engine, this, target, memberName, enumerable: true);
- }
- }
|