ArrayInstance.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using Jint.Native.Object;
  4. using Jint.Runtime;
  5. using Jint.Runtime.Descriptors;
  6. namespace Jint.Native.Array
  7. {
  8. public class ArrayInstance : ObjectInstance
  9. {
  10. private readonly Engine _engine;
  11. private IDictionary<uint, PropertyDescriptor> _array = new MruPropertyCache2<uint, PropertyDescriptor>();
  12. private PropertyDescriptor _length;
  13. public ArrayInstance(Engine engine) : base(engine)
  14. {
  15. _engine = engine;
  16. }
  17. public override string Class
  18. {
  19. get
  20. {
  21. return "Array";
  22. }
  23. }
  24. /// Implementation from ObjectInstance official specs as the one
  25. /// in ObjectInstance is optimized for the general case and wouldn't work
  26. /// for arrays
  27. public override void Put(string propertyName, JsValue value, bool throwOnError)
  28. {
  29. if (!CanPut(propertyName))
  30. {
  31. if (throwOnError)
  32. {
  33. throw new JavaScriptException(Engine.TypeError);
  34. }
  35. return;
  36. }
  37. var ownDesc = GetOwnProperty(propertyName);
  38. if (ownDesc.IsDataDescriptor())
  39. {
  40. var valueDesc = new PropertyDescriptor(value: value, writable: null, enumerable: null, configurable: null);
  41. DefineOwnProperty(propertyName, valueDesc, throwOnError);
  42. return;
  43. }
  44. // property is an accessor or inherited
  45. var desc = GetProperty(propertyName);
  46. if (desc.IsAccessorDescriptor())
  47. {
  48. var setter = desc.Set.Value.TryCast<ICallable>();
  49. setter.Call(new JsValue(this), new[] { value });
  50. }
  51. else
  52. {
  53. var newDesc = new PropertyDescriptor(value, true, true, true);
  54. DefineOwnProperty(propertyName, newDesc, throwOnError);
  55. }
  56. }
  57. public override bool DefineOwnProperty(string propertyName, PropertyDescriptor desc, bool throwOnError)
  58. {
  59. var oldLenDesc = GetOwnProperty("length");
  60. var oldLen = (uint)TypeConverter.ToNumber(oldLenDesc.Value.Value);
  61. uint index;
  62. if (propertyName == "length")
  63. {
  64. if (!desc.Value.HasValue)
  65. {
  66. return base.DefineOwnProperty("length", desc, throwOnError);
  67. }
  68. var newLenDesc = new PropertyDescriptor(desc);
  69. uint newLen = TypeConverter.ToUint32(desc.Value.Value);
  70. if (newLen != TypeConverter.ToNumber(desc.Value.Value))
  71. {
  72. throw new JavaScriptException(_engine.RangeError);
  73. }
  74. newLenDesc.Value = newLen;
  75. if (newLen >= oldLen)
  76. {
  77. return base.DefineOwnProperty("length", _length = newLenDesc, throwOnError);
  78. }
  79. if (!oldLenDesc.Writable.Value)
  80. {
  81. if (throwOnError)
  82. {
  83. throw new JavaScriptException(_engine.TypeError);
  84. }
  85. return false;
  86. }
  87. bool newWritable;
  88. if (!newLenDesc.Writable.HasValue || newLenDesc.Writable.Value)
  89. {
  90. newWritable = true;
  91. }
  92. else
  93. {
  94. newWritable = false;
  95. newLenDesc.Writable = true;
  96. }
  97. var succeeded = base.DefineOwnProperty("length", _length = newLenDesc, throwOnError);
  98. if (!succeeded)
  99. {
  100. return false;
  101. }
  102. // in the case of sparse arrays, treat each concrete element instead of
  103. // iterating over all indexes
  104. if (_array.Count < oldLen - newLen)
  105. {
  106. var keys = _array.Keys.ToArray();
  107. foreach (var key in keys)
  108. {
  109. uint keyIndex;
  110. // is it the index of the array
  111. if (IsArrayIndex(key, out keyIndex) && keyIndex >= newLen && keyIndex < oldLen)
  112. {
  113. var deleteSucceeded = Delete(key.ToString(), false);
  114. if (!deleteSucceeded)
  115. {
  116. newLenDesc.Value = new JsValue(keyIndex + 1);
  117. if (!newWritable)
  118. {
  119. newLenDesc.Writable = false;
  120. }
  121. base.DefineOwnProperty("length", _length = newLenDesc, false);
  122. if (throwOnError)
  123. {
  124. throw new JavaScriptException(_engine.TypeError);
  125. }
  126. return false;
  127. }
  128. }
  129. }
  130. }
  131. else
  132. {
  133. while (newLen < oldLen)
  134. {
  135. // algorithm as per the spec
  136. oldLen--;
  137. var deleteSucceeded = Delete(TypeConverter.ToString(oldLen), false);
  138. if (!deleteSucceeded)
  139. {
  140. newLenDesc.Value = oldLen + 1;
  141. if (!newWritable)
  142. {
  143. newLenDesc.Writable = false;
  144. }
  145. base.DefineOwnProperty("length", _length = newLenDesc, false);
  146. if (throwOnError)
  147. {
  148. throw new JavaScriptException(_engine.TypeError);
  149. }
  150. return false;
  151. }
  152. }
  153. }
  154. if (!newWritable)
  155. {
  156. DefineOwnProperty("length", new PropertyDescriptor(value: null, writable: false, enumerable: null, configurable: null), false);
  157. }
  158. return true;
  159. }
  160. else if (IsArrayIndex(propertyName, out index))
  161. {
  162. if (index >= oldLen && !oldLenDesc.Writable.Value)
  163. {
  164. if (throwOnError)
  165. {
  166. throw new JavaScriptException(_engine.TypeError);
  167. }
  168. return false;
  169. }
  170. var succeeded = base.DefineOwnProperty(propertyName, desc, false);
  171. if (!succeeded)
  172. {
  173. if (throwOnError)
  174. {
  175. throw new JavaScriptException(_engine.TypeError);
  176. }
  177. return false;
  178. }
  179. if (index >= oldLen)
  180. {
  181. oldLenDesc.Value = index + 1;
  182. base.DefineOwnProperty("length", _length = oldLenDesc, false);
  183. }
  184. return true;
  185. }
  186. return base.DefineOwnProperty(propertyName, desc, throwOnError);
  187. }
  188. private uint GetLength()
  189. {
  190. return TypeConverter.ToUint32(_length.Value.Value);
  191. }
  192. public override IEnumerable<KeyValuePair<string, PropertyDescriptor>> GetOwnProperties()
  193. {
  194. foreach(var entry in _array)
  195. {
  196. yield return new KeyValuePair<string, PropertyDescriptor>(entry.Key.ToString(), entry.Value);
  197. }
  198. foreach(var entry in base.GetOwnProperties())
  199. {
  200. yield return entry;
  201. }
  202. }
  203. public override PropertyDescriptor GetOwnProperty(string propertyName)
  204. {
  205. uint index;
  206. if (IsArrayIndex(propertyName, out index))
  207. {
  208. PropertyDescriptor result;
  209. if (_array.TryGetValue(index, out result))
  210. {
  211. return result;
  212. }
  213. else
  214. {
  215. return PropertyDescriptor.Undefined;
  216. }
  217. }
  218. return base.GetOwnProperty(propertyName);
  219. }
  220. protected override void SetOwnProperty(string propertyName, PropertyDescriptor desc)
  221. {
  222. uint index;
  223. if (IsArrayIndex(propertyName, out index))
  224. {
  225. _array[index] = desc;
  226. }
  227. else
  228. {
  229. if(propertyName == "length")
  230. {
  231. _length = desc;
  232. }
  233. base.SetOwnProperty(propertyName, desc);
  234. }
  235. }
  236. public override bool HasOwnProperty(string p)
  237. {
  238. uint index;
  239. if (IsArrayIndex(p, out index))
  240. {
  241. return index < GetLength() && _array.ContainsKey(index);
  242. }
  243. return base.HasOwnProperty(p);
  244. }
  245. public override void RemoveOwnProperty(string p)
  246. {
  247. uint index;
  248. if(IsArrayIndex(p, out index))
  249. {
  250. _array.Remove(index);
  251. }
  252. base.RemoveOwnProperty(p);
  253. }
  254. public static bool IsArrayIndex(JsValue p, out uint index)
  255. {
  256. index = ParseArrayIndex(TypeConverter.ToString(p));
  257. return index != uint.MaxValue;
  258. // 15.4 - Use an optimized version of the specification
  259. // return TypeConverter.ToString(index) == TypeConverter.ToString(p) && index != uint.MaxValue;
  260. }
  261. internal static uint ParseArrayIndex(string p)
  262. {
  263. int d = p[0] - '0';
  264. if (d < 0 || d > 9)
  265. {
  266. return uint.MaxValue;
  267. }
  268. ulong result = (uint)d;
  269. for (int i = 1; i < p.Length; i++)
  270. {
  271. d = p[i] - '0';
  272. if (d < 0 || d > 9)
  273. {
  274. return uint.MaxValue;
  275. }
  276. result = result * 10 + (uint)d;
  277. if (result >= uint.MaxValue)
  278. {
  279. return uint.MaxValue;
  280. }
  281. }
  282. return (uint)result;
  283. }
  284. }
  285. }