IndexerAccessor.cs 7.3 KB

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