IndexerAccessor.cs 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. using System.Collections;
  2. using System.Diagnostics.CodeAnalysis;
  3. using System.Globalization;
  4. using System.Reflection;
  5. using Jint.Native;
  6. using Jint.Runtime.Descriptors;
  7. using Jint.Runtime.Descriptors.Specialized;
  8. #pragma warning disable IL2067
  9. namespace Jint.Runtime.Interop.Reflection;
  10. internal sealed class IndexerAccessor : ReflectionAccessor
  11. {
  12. private readonly object _key;
  13. internal readonly PropertyInfo _indexer;
  14. private readonly MethodInfo? _getter;
  15. private readonly MethodInfo? _setter;
  16. private readonly MethodInfo? _containsKey;
  17. private IndexerAccessor(PropertyInfo indexer, MethodInfo? containsKey, object key) : base(indexer.PropertyType)
  18. {
  19. _indexer = indexer;
  20. _containsKey = containsKey;
  21. _key = key;
  22. _getter = indexer.GetGetMethod();
  23. _setter = indexer.GetSetMethod();
  24. }
  25. internal static bool TryFindIndexer(
  26. Engine engine,
  27. [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicProperties)] Type targetType,
  28. string propertyName,
  29. [NotNullWhen(true)] out IndexerAccessor? indexerAccessor,
  30. [NotNullWhen(true)] out PropertyInfo? indexer)
  31. {
  32. indexerAccessor = null;
  33. indexer = null;
  34. var paramTypeArray = new Type[1];
  35. // integer keys can be ambiguous as we only know string keys
  36. int? integerKey = null;
  37. if (int.TryParse(propertyName, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intKeyTemp))
  38. {
  39. integerKey = intKeyTemp;
  40. }
  41. IndexerAccessor? ComposeIndexerFactory(PropertyInfo candidate, Type paramType)
  42. {
  43. object? key = null;
  44. // int key is quite common case
  45. if (paramType == typeof(int) && integerKey is not null)
  46. {
  47. key = integerKey;
  48. }
  49. else
  50. {
  51. engine.TypeConverter.TryConvert(propertyName, paramType, CultureInfo.InvariantCulture, out key);
  52. }
  53. if (key is not null)
  54. {
  55. // the key can be converted for this indexer
  56. var indexerProperty = candidate;
  57. // get contains key method to avoid index exception being thrown in dictionaries
  58. paramTypeArray[0] = paramType;
  59. var containsKeyMethod = targetType.GetMethod(nameof(IDictionary<string, string>.ContainsKey), paramTypeArray);
  60. if (containsKeyMethod is null && targetType.IsAssignableFrom(typeof(IDictionary)))
  61. {
  62. paramTypeArray[0] = typeof(object);
  63. containsKeyMethod = targetType.GetMethod(nameof(IDictionary.Contains), paramTypeArray);
  64. }
  65. return new IndexerAccessor(indexerProperty, containsKeyMethod, key);
  66. }
  67. // the key type doesn't work for this indexer
  68. return null;
  69. }
  70. var filter = new Func<MemberInfo, bool>(m => engine.Options.Interop.TypeResolver.Filter(engine, m));
  71. // default indexer wins
  72. var descriptor = TypeDescriptor.Get(targetType);
  73. if (descriptor.IntegerIndexerProperty is not null && filter(descriptor.IntegerIndexerProperty))
  74. {
  75. indexerAccessor = ComposeIndexerFactory(descriptor.IntegerIndexerProperty, typeof(int));
  76. if (indexerAccessor != null)
  77. {
  78. indexer = descriptor.IntegerIndexerProperty;
  79. return true;
  80. }
  81. }
  82. // try to find first indexer having either public getter or setter with matching argument type
  83. PropertyInfo? fallbackIndexer = null;
  84. foreach (var candidate in targetType.GetProperties())
  85. {
  86. if (!filter(candidate))
  87. {
  88. continue;
  89. }
  90. var indexParameters = candidate.GetIndexParameters();
  91. if (indexParameters.Length != 1)
  92. {
  93. continue;
  94. }
  95. if (candidate.GetGetMethod() != null || candidate.GetSetMethod() != null)
  96. {
  97. var paramType = indexParameters[0].ParameterType;
  98. indexerAccessor = ComposeIndexerFactory(candidate, paramType);
  99. if (indexerAccessor != null)
  100. {
  101. if (paramType != typeof(string) || integerKey is null)
  102. {
  103. // exact match, we don't need to check for integer key
  104. indexer = candidate;
  105. return true;
  106. }
  107. if (fallbackIndexer is null)
  108. {
  109. // our fallback
  110. fallbackIndexer = candidate;
  111. }
  112. }
  113. }
  114. }
  115. if (fallbackIndexer is not null)
  116. {
  117. indexer = fallbackIndexer;
  118. // just to keep compiler happy, we know we have a value
  119. indexerAccessor ??= new IndexerAccessor(indexer, containsKey: null, key: null!);
  120. return true;
  121. }
  122. indexerAccessor = default;
  123. indexer = default;
  124. return false;
  125. }
  126. public override bool Readable => _indexer.CanRead;
  127. public override bool Writable => _indexer.CanWrite;
  128. protected override object? DoGetValue(object target, string memberName)
  129. {
  130. if (_getter is null)
  131. {
  132. ExceptionHelper.ThrowInvalidOperationException("Indexer has no public getter.");
  133. return null;
  134. }
  135. object[] parameters = { _key };
  136. if (_containsKey != null)
  137. {
  138. if (_containsKey.Invoke(target, parameters) as bool? != true)
  139. {
  140. return JsValue.Undefined;
  141. }
  142. }
  143. try
  144. {
  145. return _getter.Invoke(target, parameters);
  146. }
  147. catch (TargetInvocationException tie) when (tie.InnerException is KeyNotFoundException)
  148. {
  149. return JsValue.Undefined;
  150. }
  151. }
  152. protected override void DoSetValue(object target, string memberName, object? value)
  153. {
  154. if (_setter is null)
  155. {
  156. ExceptionHelper.ThrowInvalidOperationException("Indexer has no public setter.");
  157. }
  158. object?[] parameters = { _key, value };
  159. _setter.Invoke(target, parameters);
  160. }
  161. public override PropertyDescriptor CreatePropertyDescriptor(Engine engine, object target, string memberName, bool enumerable = true)
  162. {
  163. if (_containsKey != null)
  164. {
  165. if (_containsKey.Invoke(target, [_key]) as bool? != true)
  166. {
  167. return PropertyDescriptor.Undefined;
  168. }
  169. }
  170. return new ReflectionDescriptor(engine, this, target, memberName, enumerable: true);
  171. }
  172. }