ObjectWrapper.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Globalization;
  5. using System.Reflection;
  6. using Jint.Native;
  7. using Jint.Native.Object;
  8. using Jint.Native.Symbol;
  9. using Jint.Runtime.Descriptors;
  10. using Jint.Runtime.Descriptors.Specialized;
  11. namespace Jint.Runtime.Interop
  12. {
  13. /// <summary>
  14. /// Wraps a CLR instance
  15. /// </summary>
  16. public sealed class ObjectWrapper : ObjectInstance, IObjectWrapper
  17. {
  18. public ObjectWrapper(Engine engine, object obj)
  19. : base(engine)
  20. {
  21. Target = obj;
  22. var type = obj.GetType();
  23. if (ObjectIsArrayLikeClrCollection(type))
  24. {
  25. // create a forwarder to produce length from Count or Length if one of them is present
  26. var lengthProperty = type.GetProperty("Count") ?? type.GetProperty("Length");
  27. if (lengthProperty is null)
  28. {
  29. return;
  30. }
  31. IsArrayLike = true;
  32. var functionInstance = new ClrFunctionInstance(engine, "length", (thisObj, arguments) => JsNumber.Create((int) lengthProperty.GetValue(obj)));
  33. var descriptor = new GetSetPropertyDescriptor(functionInstance, Undefined, PropertyFlag.Configurable);
  34. SetProperty(KnownKeys.Length, descriptor);
  35. }
  36. }
  37. private static bool ObjectIsArrayLikeClrCollection(Type type)
  38. {
  39. if (typeof(ICollection).IsAssignableFrom(type))
  40. {
  41. return true;
  42. }
  43. foreach (var interfaceType in type.GetInterfaces())
  44. {
  45. if (!interfaceType.IsGenericType)
  46. {
  47. continue;
  48. }
  49. if (interfaceType.GetGenericTypeDefinition() == typeof(IReadOnlyCollection<>)
  50. || interfaceType.GetGenericTypeDefinition() == typeof(ICollection<>))
  51. {
  52. return true;
  53. }
  54. }
  55. return false;
  56. }
  57. public object Target { get; }
  58. public override bool IsArrayLike { get; }
  59. public override bool Set(JsValue property, JsValue value, JsValue receiver)
  60. {
  61. if (!CanPut(property))
  62. {
  63. return false;
  64. }
  65. var ownDesc = GetOwnProperty(property);
  66. if (ownDesc == null)
  67. {
  68. return false;
  69. }
  70. ownDesc.Value = value;
  71. return true;
  72. }
  73. public override JsValue Get(JsValue property, JsValue receiver)
  74. {
  75. if (property.IsSymbol())
  76. {
  77. // wrapped objects cannot have symbol properties
  78. return Undefined;
  79. }
  80. return base.Get(property, receiver);
  81. }
  82. public override List<JsValue> GetOwnPropertyKeys(Types types = Types.None | Types.String | Types.Symbol)
  83. {
  84. return new List<JsValue>(EnumerateOwnPropertyKeys(types));
  85. }
  86. public override IEnumerable<KeyValuePair<JsValue, PropertyDescriptor>> GetOwnProperties()
  87. {
  88. foreach (var key in EnumerateOwnPropertyKeys(Types.String | Types.Symbol))
  89. {
  90. yield return new KeyValuePair<JsValue, PropertyDescriptor>(key, GetOwnProperty(key));
  91. }
  92. }
  93. private IEnumerable<JsValue> EnumerateOwnPropertyKeys(Types types)
  94. {
  95. var basePropertyKeys = base.GetOwnPropertyKeys(types);
  96. // prefer object order, add possible other properties after
  97. var processed = basePropertyKeys.Count > 0 ? new HashSet<JsValue>() : null;
  98. var includeStrings = (types & Types.String) != 0;
  99. if (Target is IDictionary dictionary && includeStrings)
  100. {
  101. // we take values exposed as dictionary keys only
  102. foreach (var key in dictionary.Keys)
  103. {
  104. if (_engine.ClrTypeConverter.TryConvert(key, typeof(string), CultureInfo.InvariantCulture, out var stringKey))
  105. {
  106. var jsString = JsString.Create((string) stringKey);
  107. processed?.Add(jsString);
  108. yield return jsString;
  109. }
  110. }
  111. }
  112. else if (includeStrings)
  113. {
  114. // we take public properties and fields
  115. var type = Target.GetType();
  116. foreach (var p in type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public))
  117. {
  118. var indexParameters = p.GetIndexParameters();
  119. if (indexParameters.Length == 0)
  120. {
  121. var jsString = JsString.Create(p.Name);
  122. processed?.Add(jsString);
  123. yield return jsString;
  124. }
  125. }
  126. foreach (var f in type.GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public))
  127. {
  128. var jsString = JsString.Create(f.Name);
  129. processed?.Add(jsString);
  130. yield return jsString;
  131. }
  132. }
  133. if (processed != null)
  134. {
  135. // we have base keys
  136. for (var i = 0; i < basePropertyKeys.Count; i++)
  137. {
  138. var key = basePropertyKeys[i];
  139. if (processed.Add(key))
  140. {
  141. yield return key;
  142. }
  143. }
  144. }
  145. }
  146. public override PropertyDescriptor GetOwnProperty(JsValue property)
  147. {
  148. if (TryGetProperty(property, out var x))
  149. {
  150. return x;
  151. }
  152. if (property.IsSymbol() && property == GlobalSymbolRegistry.Iterator)
  153. {
  154. var iteratorFunction = new ClrFunctionInstance(Engine, "iterator", (thisObject, arguments) => _engine.Iterator.Construct(this), 1, PropertyFlag.Configurable);
  155. var iteratorProperty = new PropertyDescriptor(iteratorFunction, PropertyFlag.Configurable | PropertyFlag.Writable);
  156. SetProperty(GlobalSymbolRegistry.Iterator, iteratorProperty);
  157. return iteratorProperty;
  158. }
  159. var memberAccessor = Engine.Options._MemberAccessor;
  160. if (memberAccessor != null)
  161. {
  162. var result = memberAccessor.Invoke(Engine, Target, property.ToString());
  163. if (result != null)
  164. {
  165. return new PropertyDescriptor(result, PropertyFlag.OnlyEnumerable);
  166. }
  167. }
  168. var type = Target.GetType();
  169. var key = new ClrPropertyDescriptorFactoriesKey(type, property.ToString());
  170. if (!_engine.ClrPropertyDescriptorFactories.TryGetValue(key, out var factory))
  171. {
  172. factory = ResolveProperty(type, property.ToString());
  173. _engine.ClrPropertyDescriptorFactories[key] = factory;
  174. }
  175. var descriptor = factory(_engine, Target);
  176. AddProperty(property, descriptor);
  177. return descriptor;
  178. }
  179. private Func<Engine, object, PropertyDescriptor> ResolveProperty(Type type, string propertyName)
  180. {
  181. var isNumber = uint.TryParse(propertyName, out _);
  182. // properties and fields cannot be numbers
  183. if (!isNumber)
  184. {
  185. // look for a property, bit be wary of indexers, we don't want indexers which have name "Item" to take precedence
  186. PropertyInfo property = null;
  187. foreach (var p in type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public))
  188. {
  189. // only if it's not an indexer, we can do case-ignoring matches
  190. var isStandardIndexer = p.GetIndexParameters().Length == 1 && p.Name == "Item";
  191. if (!isStandardIndexer && EqualsIgnoreCasing(p.Name, propertyName))
  192. {
  193. property = p;
  194. break;
  195. }
  196. }
  197. if (property != null)
  198. {
  199. return (engine, target) => new PropertyInfoDescriptor(engine, property, target);
  200. }
  201. // look for a field
  202. FieldInfo field = null;
  203. foreach (var f in type.GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public))
  204. {
  205. if (EqualsIgnoreCasing(f.Name, propertyName))
  206. {
  207. field = f;
  208. break;
  209. }
  210. }
  211. if (field != null)
  212. {
  213. return (engine, target) => new FieldInfoDescriptor(engine, field, target);
  214. }
  215. // if no properties were found then look for a method
  216. List<MethodInfo> methods = null;
  217. foreach (var m in type.GetMethods(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public))
  218. {
  219. if (EqualsIgnoreCasing(m.Name, propertyName))
  220. {
  221. methods ??= new List<MethodInfo>();
  222. methods.Add(m);
  223. }
  224. }
  225. if (methods?.Count > 0)
  226. {
  227. return (engine, target) => new PropertyDescriptor(new MethodInfoFunctionInstance(engine, methods.ToArray()), PropertyFlag.OnlyEnumerable);
  228. }
  229. }
  230. // if no methods are found check if target implemented indexing
  231. if (IndexDescriptor.TryFindIndexer(_engine, type, propertyName, out _, out _, out _))
  232. {
  233. return (engine, target) => new IndexDescriptor(engine, propertyName, target);
  234. }
  235. // try to find a single explicit property implementation
  236. List<PropertyInfo> list = null;
  237. foreach (Type iface in type.GetInterfaces())
  238. {
  239. foreach (var iprop in iface.GetProperties())
  240. {
  241. if (EqualsIgnoreCasing(iprop.Name, propertyName))
  242. {
  243. list ??= new List<PropertyInfo>();
  244. list.Add(iprop);
  245. }
  246. }
  247. }
  248. if (list?.Count == 1)
  249. {
  250. return (engine, target) => new PropertyInfoDescriptor(engine, list[0], target);
  251. }
  252. // try to find explicit method implementations
  253. List<MethodInfo> explicitMethods = null;
  254. foreach (Type iface in type.GetInterfaces())
  255. {
  256. foreach (var imethod in iface.GetMethods())
  257. {
  258. if (EqualsIgnoreCasing(imethod.Name, propertyName))
  259. {
  260. explicitMethods ??= new List<MethodInfo>();
  261. explicitMethods.Add(imethod);
  262. }
  263. }
  264. }
  265. if (explicitMethods?.Count > 0)
  266. {
  267. return (engine, target) => new PropertyDescriptor(new MethodInfoFunctionInstance(engine, explicitMethods.ToArray()), PropertyFlag.OnlyEnumerable);
  268. }
  269. // try to find explicit indexer implementations
  270. foreach (Type interfaceType in type.GetInterfaces())
  271. {
  272. if (IndexDescriptor.TryFindIndexer(_engine, interfaceType, propertyName, out _, out _, out _))
  273. {
  274. return (engine, target) => new IndexDescriptor(engine, interfaceType, propertyName, target);
  275. }
  276. }
  277. return (engine, target) => PropertyDescriptor.Undefined;
  278. }
  279. private static bool EqualsIgnoreCasing(string s1, string s2)
  280. {
  281. bool equals = false;
  282. if (s1.Length == s2.Length)
  283. {
  284. if (s1.Length > 0)
  285. {
  286. equals = char.ToLowerInvariant(s1[0]) == char.ToLowerInvariant(s2[0]);
  287. }
  288. if (equals && s1.Length > 1)
  289. {
  290. equals = s1.Substring(1) == s2.Substring(1);
  291. }
  292. }
  293. return equals;
  294. }
  295. }
  296. }