ArrayInstance.cs 11 KB

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