PageRenderTime 65ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/js/lib/Socket.IO-node/support/expresso/deps/jscoverage/js/jsarray.cpp

http://github.com/onedayitwillmake/RealtimeMultiplayerNodeJs
C++ | 2159 lines | 1567 code | 251 blank | 341 comment | 398 complexity | df71ecc54001a084e456bc2e9727708b MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, MPL-2.0-no-copyleft-exception, BSD-3-Clause
  1. /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  2. * vim: set sw=4 ts=8 et tw=78:
  3. *
  4. * ***** BEGIN LICENSE BLOCK *****
  5. * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  6. *
  7. * The contents of this file are subject to the Mozilla Public License Version
  8. * 1.1 (the "License"); you may not use this file except in compliance with
  9. * the License. You may obtain a copy of the License at
  10. * http://www.mozilla.org/MPL/
  11. *
  12. * Software distributed under the License is distributed on an "AS IS" basis,
  13. * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  14. * for the specific language governing rights and limitations under the
  15. * License.
  16. *
  17. * The Original Code is Mozilla Communicator client code, released
  18. * March 31, 1998.
  19. *
  20. * The Initial Developer of the Original Code is
  21. * Netscape Communications Corporation.
  22. * Portions created by the Initial Developer are Copyright (C) 1998
  23. * the Initial Developer. All Rights Reserved.
  24. *
  25. * Contributor(s):
  26. *
  27. * Alternatively, the contents of this file may be used under the terms of
  28. * either of the GNU General Public License Version 2 or later (the "GPL"),
  29. * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  30. * in which case the provisions of the GPL or the LGPL are applicable instead
  31. * of those above. If you wish to allow use of your version of this file only
  32. * under the terms of either the GPL or the LGPL, and not to allow others to
  33. * use your version of this file under the terms of the MPL, indicate your
  34. * decision by deleting the provisions above and replace them with the notice
  35. * and other provisions required by the GPL or the LGPL. If you do not delete
  36. * the provisions above, a recipient may use your version of this file under
  37. * the terms of any one of the MPL, the GPL or the LGPL.
  38. *
  39. * ***** END LICENSE BLOCK ***** */
  40. /*
  41. * JS array class.
  42. *
  43. * Array objects begin as "dense" arrays, optimized for numeric-only property
  44. * access over a vector of slots (obj->dslots) with high load factor. Array
  45. * methods optimize for denseness by testing that the object's class is
  46. * &js_ArrayClass, and can then directly manipulate the slots for efficiency.
  47. *
  48. * We track these pieces of metadata for arrays in dense mode:
  49. * - the array's length property as a uint32, in JSSLOT_ARRAY_LENGTH,
  50. * - the number of indices that are filled (non-holes), in JSSLOT_ARRAY_COUNT,
  51. * - the net number of slots starting at dslots (DENSELEN), in dslots[-1] if
  52. * dslots is non-NULL.
  53. *
  54. * In dense mode, holes in the array are represented by JSVAL_HOLE. The final
  55. * slot in fslots (JSSLOT_ARRAY_LOOKUP_HOLDER) is used to store the single jsid
  56. * "in use" by a lookupProperty caller.
  57. *
  58. * Arrays are converted to use js_SlowArrayClass when any of these conditions
  59. * are met:
  60. * - the load factor (COUNT / DENSELEN) is less than 0.25, and there are
  61. * more than MIN_SPARSE_INDEX slots total
  62. * - a property is set that is non-numeric (and not "length"); or
  63. * - a hole is filled below DENSELEN (possibly implicitly through methods like
  64. * |reverse| or |splice|).
  65. *
  66. * In the latter two cases, property creation order is no longer index order,
  67. * which necessitates use of a structure that keeps track of property creation
  68. * order. (ES4, due to expectations baked into web script, requires that
  69. * enumeration order be the order in which properties were created.)
  70. *
  71. * An alternative in the latter case (out-of-order index set) would be to
  72. * maintain the scope to track property enumeration order, but still use
  73. * the fast slot access. That would have the same memory cost as just using
  74. * a js_SlowArrayClass, but have the same performance characteristics as
  75. * a dense array for slot accesses, at some cost in code complexity.
  76. */
  77. #include "jsstddef.h"
  78. #include <stdlib.h>
  79. #include <string.h>
  80. #include "jstypes.h"
  81. #include "jsutil.h" /* Added by JSIFY */
  82. #include "jsapi.h"
  83. #include "jsarray.h"
  84. #include "jsatom.h"
  85. #include "jsbit.h"
  86. #include "jsbool.h"
  87. #include "jsbuiltins.h"
  88. #include "jscntxt.h"
  89. #include "jsversion.h"
  90. #include "jsdbgapi.h" /* for js_TraceWatchPoints */
  91. #include "jsdtoa.h"
  92. #include "jsfun.h"
  93. #include "jsgc.h"
  94. #include "jsinterp.h"
  95. #include "jslock.h"
  96. #include "jsnum.h"
  97. #include "jsobj.h"
  98. #include "jsscope.h"
  99. #include "jsstr.h"
  100. #include "jsstaticcheck.h"
  101. /* 2^32 - 1 as a number and a string */
  102. #define MAXINDEX 4294967295u
  103. #define MAXSTR "4294967295"
  104. /* Small arrays are dense, no matter what. */
  105. #define MIN_SPARSE_INDEX 32
  106. #define INDEX_TOO_BIG(index) ((index) > JS_BIT(29) - 1)
  107. #define INDEX_TOO_SPARSE(array, index) \
  108. (INDEX_TOO_BIG(index) || \
  109. ((index) > ARRAY_DENSE_LENGTH(array) && (index) >= MIN_SPARSE_INDEX && \
  110. (index) > (uint32)((array)->fslots[JSSLOT_ARRAY_COUNT] + 1) * 4))
  111. JS_STATIC_ASSERT(sizeof(JSScopeProperty) > 4 * sizeof(jsval));
  112. #define ENSURE_SLOW_ARRAY(cx, obj) \
  113. (OBJ_GET_CLASS(cx, obj) == &js_SlowArrayClass || js_MakeArraySlow(cx, obj))
  114. /*
  115. * Determine if the id represents an array index or an XML property index.
  116. *
  117. * An id is an array index according to ECMA by (15.4):
  118. *
  119. * "Array objects give special treatment to a certain class of property names.
  120. * A property name P (in the form of a string value) is an array index if and
  121. * only if ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal
  122. * to 2^32-1."
  123. *
  124. * In our implementation, it would be sufficient to check for JSVAL_IS_INT(id)
  125. * except that by using signed 32-bit integers we miss the top half of the
  126. * valid range. This function checks the string representation itself; note
  127. * that calling a standard conversion routine might allow strings such as
  128. * "08" or "4.0" as array indices, which they are not.
  129. */
  130. JSBool
  131. js_IdIsIndex(jsval id, jsuint *indexp)
  132. {
  133. JSString *str;
  134. jschar *cp;
  135. if (JSVAL_IS_INT(id)) {
  136. jsint i;
  137. i = JSVAL_TO_INT(id);
  138. if (i < 0)
  139. return JS_FALSE;
  140. *indexp = (jsuint)i;
  141. return JS_TRUE;
  142. }
  143. /* NB: id should be a string, but jsxml.c may call us with an object id. */
  144. if (!JSVAL_IS_STRING(id))
  145. return JS_FALSE;
  146. str = JSVAL_TO_STRING(id);
  147. cp = JSSTRING_CHARS(str);
  148. if (JS7_ISDEC(*cp) && JSSTRING_LENGTH(str) < sizeof(MAXSTR)) {
  149. jsuint index = JS7_UNDEC(*cp++);
  150. jsuint oldIndex = 0;
  151. jsuint c = 0;
  152. if (index != 0) {
  153. while (JS7_ISDEC(*cp)) {
  154. oldIndex = index;
  155. c = JS7_UNDEC(*cp);
  156. index = 10*index + c;
  157. cp++;
  158. }
  159. }
  160. /* Ensure that all characters were consumed and we didn't overflow. */
  161. if (*cp == 0 &&
  162. (oldIndex < (MAXINDEX / 10) ||
  163. (oldIndex == (MAXINDEX / 10) && c < (MAXINDEX % 10))))
  164. {
  165. *indexp = index;
  166. return JS_TRUE;
  167. }
  168. }
  169. return JS_FALSE;
  170. }
  171. static jsuint
  172. ValueIsLength(JSContext *cx, jsval* vp)
  173. {
  174. jsint i;
  175. jsdouble d;
  176. jsuint length;
  177. if (JSVAL_IS_INT(*vp)) {
  178. i = JSVAL_TO_INT(*vp);
  179. if (i < 0)
  180. goto error;
  181. return (jsuint) i;
  182. }
  183. d = js_ValueToNumber(cx, vp);
  184. if (JSVAL_IS_NULL(*vp))
  185. goto error;
  186. if (JSDOUBLE_IS_NaN(d))
  187. goto error;
  188. length = (jsuint) d;
  189. if (d != (jsdouble) length)
  190. goto error;
  191. return length;
  192. error:
  193. JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
  194. JSMSG_BAD_ARRAY_LENGTH);
  195. *vp = JSVAL_NULL;
  196. return 0;
  197. }
  198. JSBool
  199. js_GetLengthProperty(JSContext *cx, JSObject *obj, jsuint *lengthp)
  200. {
  201. JSTempValueRooter tvr;
  202. jsid id;
  203. JSBool ok;
  204. jsint i;
  205. if (OBJ_IS_ARRAY(cx, obj)) {
  206. *lengthp = obj->fslots[JSSLOT_ARRAY_LENGTH];
  207. return JS_TRUE;
  208. }
  209. JS_PUSH_SINGLE_TEMP_ROOT(cx, JSVAL_NULL, &tvr);
  210. id = ATOM_TO_JSID(cx->runtime->atomState.lengthAtom);
  211. ok = OBJ_GET_PROPERTY(cx, obj, id, &tvr.u.value);
  212. if (ok) {
  213. if (JSVAL_IS_INT(tvr.u.value)) {
  214. i = JSVAL_TO_INT(tvr.u.value);
  215. *lengthp = (jsuint)i; /* jsuint cast does ToUint32 */
  216. } else {
  217. *lengthp = js_ValueToECMAUint32(cx, &tvr.u.value);
  218. ok = !JSVAL_IS_NULL(tvr.u.value);
  219. }
  220. }
  221. JS_POP_TEMP_ROOT(cx, &tvr);
  222. return ok;
  223. }
  224. static JSBool
  225. IndexToValue(JSContext *cx, jsuint index, jsval *vp)
  226. {
  227. if (index <= JSVAL_INT_MAX) {
  228. *vp = INT_TO_JSVAL(index);
  229. return JS_TRUE;
  230. }
  231. return JS_NewDoubleValue(cx, (jsdouble)index, vp);
  232. }
  233. JSBool JS_FASTCALL
  234. js_IndexToId(JSContext *cx, jsuint index, jsid *idp)
  235. {
  236. JSString *str;
  237. if (index <= JSVAL_INT_MAX) {
  238. *idp = INT_TO_JSID(index);
  239. return JS_TRUE;
  240. }
  241. str = js_NumberToString(cx, index);
  242. if (!str)
  243. return JS_FALSE;
  244. return js_ValueToStringId(cx, STRING_TO_JSVAL(str), idp);
  245. }
  246. static JSBool
  247. BigIndexToId(JSContext *cx, JSObject *obj, jsuint index, JSBool createAtom,
  248. jsid *idp)
  249. {
  250. jschar buf[10], *start;
  251. JSClass *clasp;
  252. JSAtom *atom;
  253. JS_STATIC_ASSERT((jsuint)-1 == 4294967295U);
  254. JS_ASSERT(index > JSVAL_INT_MAX);
  255. start = JS_ARRAY_END(buf);
  256. do {
  257. --start;
  258. *start = (jschar)('0' + index % 10);
  259. index /= 10;
  260. } while (index != 0);
  261. /*
  262. * Skip the atomization if the class is known to store atoms corresponding
  263. * to big indexes together with elements. In such case we know that the
  264. * array does not have an element at the given index if its atom does not
  265. * exist. Fast arrays (clasp == &js_ArrayClass) don't use atoms for
  266. * any indexes, though it would be rare to see them have a big index
  267. * in any case.
  268. */
  269. if (!createAtom &&
  270. ((clasp = OBJ_GET_CLASS(cx, obj)) == &js_SlowArrayClass ||
  271. clasp == &js_ArgumentsClass ||
  272. clasp == &js_ObjectClass)) {
  273. atom = js_GetExistingStringAtom(cx, start, JS_ARRAY_END(buf) - start);
  274. if (!atom) {
  275. *idp = JSVAL_VOID;
  276. return JS_TRUE;
  277. }
  278. } else {
  279. atom = js_AtomizeChars(cx, start, JS_ARRAY_END(buf) - start, 0);
  280. if (!atom)
  281. return JS_FALSE;
  282. }
  283. *idp = ATOM_TO_JSID(atom);
  284. return JS_TRUE;
  285. }
  286. static JSBool
  287. ResizeSlots(JSContext *cx, JSObject *obj, uint32 oldlen, uint32 len)
  288. {
  289. jsval *slots, *newslots;
  290. if (len == 0) {
  291. if (obj->dslots) {
  292. JS_free(cx, obj->dslots - 1);
  293. obj->dslots = NULL;
  294. }
  295. return JS_TRUE;
  296. }
  297. if (len > ~(uint32)0 / sizeof(jsval)) {
  298. js_ReportAllocationOverflow(cx);
  299. return JS_FALSE;
  300. }
  301. slots = obj->dslots ? obj->dslots - 1 : NULL;
  302. newslots = (jsval *) JS_realloc(cx, slots, sizeof (jsval) * (len + 1));
  303. if (!newslots)
  304. return JS_FALSE;
  305. obj->dslots = newslots + 1;
  306. ARRAY_SET_DENSE_LENGTH(obj, len);
  307. for (slots = obj->dslots + oldlen; slots < obj->dslots + len; slots++)
  308. *slots = JSVAL_HOLE;
  309. return JS_TRUE;
  310. }
  311. static JSBool
  312. EnsureLength(JSContext *cx, JSObject *obj, uint32 len)
  313. {
  314. uint32 oldlen = ARRAY_DENSE_LENGTH(obj);
  315. if (len > oldlen) {
  316. return ResizeSlots(cx, obj, oldlen,
  317. len + ARRAY_GROWBY - (len % ARRAY_GROWBY));
  318. }
  319. return JS_TRUE;
  320. }
  321. /*
  322. * If the property at the given index exists, get its value into location
  323. * pointed by vp and set *hole to false. Otherwise set *hole to true and *vp
  324. * to JSVAL_VOID. This function assumes that the location pointed by vp is
  325. * properly rooted and can be used as GC-protected storage for temporaries.
  326. */
  327. static JSBool
  328. GetArrayElement(JSContext *cx, JSObject *obj, jsuint index, JSBool *hole,
  329. jsval *vp)
  330. {
  331. jsid id;
  332. JSObject *obj2;
  333. JSProperty *prop;
  334. if (OBJ_IS_DENSE_ARRAY(cx, obj) && index < ARRAY_DENSE_LENGTH(obj) &&
  335. (*vp = obj->dslots[index]) != JSVAL_HOLE) {
  336. *hole = JS_FALSE;
  337. return JS_TRUE;
  338. }
  339. if (index <= JSVAL_INT_MAX) {
  340. id = INT_TO_JSID(index);
  341. } else {
  342. if (!BigIndexToId(cx, obj, index, JS_FALSE, &id))
  343. return JS_FALSE;
  344. if (JSVAL_IS_VOID(id)) {
  345. *hole = JS_TRUE;
  346. *vp = JSVAL_VOID;
  347. return JS_TRUE;
  348. }
  349. }
  350. if (!OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop))
  351. return JS_FALSE;
  352. if (!prop) {
  353. *hole = JS_TRUE;
  354. *vp = JSVAL_VOID;
  355. } else {
  356. OBJ_DROP_PROPERTY(cx, obj2, prop);
  357. if (!OBJ_GET_PROPERTY(cx, obj, id, vp))
  358. return JS_FALSE;
  359. *hole = JS_FALSE;
  360. }
  361. return JS_TRUE;
  362. }
  363. /*
  364. * Set the value of the property at the given index to v assuming v is rooted.
  365. */
  366. static JSBool
  367. SetArrayElement(JSContext *cx, JSObject *obj, jsuint index, jsval v)
  368. {
  369. jsid id;
  370. if (OBJ_IS_DENSE_ARRAY(cx, obj)) {
  371. /* Predicted/prefeched code should favor the remains-dense case. */
  372. if (!INDEX_TOO_SPARSE(obj, index)) {
  373. if (!EnsureLength(cx, obj, index + 1))
  374. return JS_FALSE;
  375. if (index >= (uint32)obj->fslots[JSSLOT_ARRAY_LENGTH])
  376. obj->fslots[JSSLOT_ARRAY_LENGTH] = index + 1;
  377. if (obj->dslots[index] == JSVAL_HOLE)
  378. obj->fslots[JSSLOT_ARRAY_COUNT]++;
  379. obj->dslots[index] = v;
  380. return JS_TRUE;
  381. }
  382. if (!js_MakeArraySlow(cx, obj))
  383. return JS_FALSE;
  384. }
  385. if (index <= JSVAL_INT_MAX) {
  386. id = INT_TO_JSID(index);
  387. } else {
  388. if (!BigIndexToId(cx, obj, index, JS_TRUE, &id))
  389. return JS_FALSE;
  390. JS_ASSERT(!JSVAL_IS_VOID(id));
  391. }
  392. return OBJ_SET_PROPERTY(cx, obj, id, &v);
  393. }
  394. static JSBool
  395. DeleteArrayElement(JSContext *cx, JSObject *obj, jsuint index)
  396. {
  397. jsid id;
  398. jsval junk;
  399. if (OBJ_IS_DENSE_ARRAY(cx, obj)) {
  400. if (index < ARRAY_DENSE_LENGTH(obj)) {
  401. if (obj->dslots[index] != JSVAL_HOLE)
  402. obj->fslots[JSSLOT_ARRAY_COUNT]--;
  403. obj->dslots[index] = JSVAL_HOLE;
  404. }
  405. return JS_TRUE;
  406. }
  407. if (index <= JSVAL_INT_MAX) {
  408. id = INT_TO_JSID(index);
  409. } else {
  410. if (!BigIndexToId(cx, obj, index, JS_FALSE, &id))
  411. return JS_FALSE;
  412. if (JSVAL_IS_VOID(id))
  413. return JS_TRUE;
  414. }
  415. return OBJ_DELETE_PROPERTY(cx, obj, id, &junk);
  416. }
  417. /*
  418. * When hole is true, delete the property at the given index. Otherwise set
  419. * its value to v assuming v is rooted.
  420. */
  421. static JSBool
  422. SetOrDeleteArrayElement(JSContext *cx, JSObject *obj, jsuint index,
  423. JSBool hole, jsval v)
  424. {
  425. if (hole) {
  426. JS_ASSERT(JSVAL_IS_VOID(v));
  427. return DeleteArrayElement(cx, obj, index);
  428. }
  429. return SetArrayElement(cx, obj, index, v);
  430. }
  431. JSBool
  432. js_SetLengthProperty(JSContext *cx, JSObject *obj, jsuint length)
  433. {
  434. jsval v;
  435. jsid id;
  436. if (!IndexToValue(cx, length, &v))
  437. return JS_FALSE;
  438. id = ATOM_TO_JSID(cx->runtime->atomState.lengthAtom);
  439. return OBJ_SET_PROPERTY(cx, obj, id, &v);
  440. }
  441. JSBool
  442. js_HasLengthProperty(JSContext *cx, JSObject *obj, jsuint *lengthp)
  443. {
  444. JSErrorReporter older;
  445. JSTempValueRooter tvr;
  446. jsid id;
  447. JSBool ok;
  448. older = JS_SetErrorReporter(cx, NULL);
  449. JS_PUSH_SINGLE_TEMP_ROOT(cx, JSVAL_NULL, &tvr);
  450. id = ATOM_TO_JSID(cx->runtime->atomState.lengthAtom);
  451. ok = OBJ_GET_PROPERTY(cx, obj, id, &tvr.u.value);
  452. JS_SetErrorReporter(cx, older);
  453. if (ok) {
  454. *lengthp = ValueIsLength(cx, &tvr.u.value);
  455. ok = !JSVAL_IS_NULL(tvr.u.value);
  456. }
  457. JS_POP_TEMP_ROOT(cx, &tvr);
  458. return ok;
  459. }
  460. JSBool
  461. js_IsArrayLike(JSContext *cx, JSObject *obj, JSBool *answerp, jsuint *lengthp)
  462. {
  463. JSClass *clasp;
  464. clasp = OBJ_GET_CLASS(cx, obj);
  465. *answerp = (clasp == &js_ArgumentsClass || clasp == &js_ArrayClass ||
  466. clasp == &js_SlowArrayClass);
  467. if (!*answerp) {
  468. *lengthp = 0;
  469. return JS_TRUE;
  470. }
  471. return js_GetLengthProperty(cx, obj, lengthp);
  472. }
  473. /*
  474. * The 'length' property of all native Array instances is a shared permanent
  475. * property of Array.prototype, so it appears to be a direct property of each
  476. * array instance delegating to that Array.prototype. It accesses the private
  477. * slot reserved by js_ArrayClass.
  478. *
  479. * Since SpiderMonkey supports cross-class prototype-based delegation, we have
  480. * to be careful about the length getter and setter being called on an object
  481. * not of Array class. For the getter, we search obj's prototype chain for the
  482. * array that caused this getter to be invoked. In the setter case to overcome
  483. * the JSPROP_SHARED attribute, we must define a shadowing length property.
  484. */
  485. static JSBool
  486. array_length_getter(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
  487. {
  488. do {
  489. if (OBJ_IS_ARRAY(cx, obj))
  490. return IndexToValue(cx, obj->fslots[JSSLOT_ARRAY_LENGTH], vp);
  491. } while ((obj = OBJ_GET_PROTO(cx, obj)) != NULL);
  492. return JS_TRUE;
  493. }
  494. static JSBool
  495. array_length_setter(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
  496. {
  497. jsuint newlen, oldlen, gap, index;
  498. jsval junk;
  499. JSObject *iter;
  500. JSTempValueRooter tvr;
  501. JSBool ok;
  502. if (!OBJ_IS_ARRAY(cx, obj)) {
  503. jsid lengthId = ATOM_TO_JSID(cx->runtime->atomState.lengthAtom);
  504. return OBJ_DEFINE_PROPERTY(cx, obj, lengthId, *vp, NULL, NULL,
  505. JSPROP_ENUMERATE, NULL);
  506. }
  507. newlen = ValueIsLength(cx, vp);
  508. if (JSVAL_IS_NULL(*vp))
  509. return JS_FALSE;
  510. oldlen = obj->fslots[JSSLOT_ARRAY_LENGTH];
  511. if (oldlen == newlen)
  512. return JS_TRUE;
  513. if (!IndexToValue(cx, newlen, vp))
  514. return JS_FALSE;
  515. if (oldlen < newlen) {
  516. obj->fslots[JSSLOT_ARRAY_LENGTH] = newlen;
  517. return JS_TRUE;
  518. }
  519. if (OBJ_IS_DENSE_ARRAY(cx, obj)) {
  520. if (ARRAY_DENSE_LENGTH(obj) && !ResizeSlots(cx, obj, oldlen, newlen))
  521. return JS_FALSE;
  522. } else if (oldlen - newlen < (1 << 24)) {
  523. do {
  524. --oldlen;
  525. if (!JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) ||
  526. !DeleteArrayElement(cx, obj, oldlen)) {
  527. return JS_FALSE;
  528. }
  529. } while (oldlen != newlen);
  530. } else {
  531. /*
  532. * We are going to remove a lot of indexes in a presumably sparse
  533. * array. So instead of looping through indexes between newlen and
  534. * oldlen, we iterate through all properties and remove those that
  535. * correspond to indexes in the half-open range [newlen, oldlen). See
  536. * bug 322135.
  537. */
  538. iter = JS_NewPropertyIterator(cx, obj);
  539. if (!iter)
  540. return JS_FALSE;
  541. /* Protect iter against GC in OBJ_DELETE_PROPERTY. */
  542. JS_PUSH_TEMP_ROOT_OBJECT(cx, iter, &tvr);
  543. gap = oldlen - newlen;
  544. for (;;) {
  545. ok = (JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) &&
  546. JS_NextProperty(cx, iter, &id));
  547. if (!ok)
  548. break;
  549. if (JSVAL_IS_VOID(id))
  550. break;
  551. if (js_IdIsIndex(id, &index) && index - newlen < gap) {
  552. ok = OBJ_DELETE_PROPERTY(cx, obj, id, &junk);
  553. if (!ok)
  554. break;
  555. }
  556. }
  557. JS_POP_TEMP_ROOT(cx, &tvr);
  558. if (!ok)
  559. return JS_FALSE;
  560. }
  561. obj->fslots[JSSLOT_ARRAY_LENGTH] = newlen;
  562. return JS_TRUE;
  563. }
  564. static JSBool
  565. array_lookupProperty(JSContext *cx, JSObject *obj, jsid id, JSObject **objp,
  566. JSProperty **propp)
  567. {
  568. uint32 i;
  569. union { JSProperty *p; jsval *v; } u;
  570. if (!OBJ_IS_DENSE_ARRAY(cx, obj))
  571. return js_LookupProperty(cx, obj, id, objp, propp);
  572. /*
  573. * We have only indexed properties up to DENSELEN (excepting holes), plus
  574. * the length property. For all else, we delegate to the prototype.
  575. */
  576. if (id != ATOM_TO_JSID(cx->runtime->atomState.lengthAtom) &&
  577. (!js_IdIsIndex(id, &i) ||
  578. obj->fslots[JSSLOT_ARRAY_LENGTH] == 0 ||
  579. i >= ARRAY_DENSE_LENGTH(obj) ||
  580. obj->dslots[i] == JSVAL_HOLE))
  581. {
  582. JSObject *proto = STOBJ_GET_PROTO(obj);
  583. if (!proto) {
  584. *objp = NULL;
  585. *propp = NULL;
  586. return JS_TRUE;
  587. }
  588. return OBJ_LOOKUP_PROPERTY(cx, proto, id, objp, propp);
  589. }
  590. /* FIXME 417501: threadsafety: could race with a lookup on another thread.
  591. * If we can only have a single lookup active per context, we could
  592. * pigeonhole this on the context instead. */
  593. JS_ASSERT(JSVAL_IS_VOID(obj->fslots[JSSLOT_ARRAY_LOOKUP_HOLDER]));
  594. obj->fslots[JSSLOT_ARRAY_LOOKUP_HOLDER] = (jsval) id;
  595. u.v = &(obj->fslots[JSSLOT_ARRAY_LOOKUP_HOLDER]);
  596. *propp = u.p;
  597. *objp = obj;
  598. return JS_TRUE;
  599. }
  600. static void
  601. array_dropProperty(JSContext *cx, JSObject *obj, JSProperty *prop)
  602. {
  603. JS_ASSERT_IF(OBJ_IS_DENSE_ARRAY(cx, obj),
  604. !JSVAL_IS_VOID(obj->fslots[JSSLOT_ARRAY_LOOKUP_HOLDER]));
  605. #ifdef DEBUG
  606. obj->fslots[JSSLOT_ARRAY_LOOKUP_HOLDER] = JSVAL_VOID;
  607. #endif
  608. }
  609. static JSBool
  610. array_getProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
  611. {
  612. uint32 i;
  613. if (id == ATOM_TO_JSID(cx->runtime->atomState.lengthAtom))
  614. return IndexToValue(cx, obj->fslots[JSSLOT_ARRAY_LENGTH], vp);
  615. if (id == ATOM_TO_JSID(cx->runtime->atomState.protoAtom)) {
  616. *vp = STOBJ_GET_SLOT(obj, JSSLOT_PROTO);
  617. return JS_TRUE;
  618. }
  619. if (!OBJ_IS_DENSE_ARRAY(cx, obj))
  620. return js_GetProperty(cx, obj, id, vp);
  621. if (!js_IdIsIndex(ID_TO_VALUE(id), &i) || i >= ARRAY_DENSE_LENGTH(obj) ||
  622. obj->dslots[i] == JSVAL_HOLE) {
  623. JSObject *obj2;
  624. JSProperty *prop;
  625. JSScopeProperty *sprop;
  626. JSObject *proto = STOBJ_GET_PROTO(obj);
  627. if (!proto) {
  628. *vp = JSVAL_VOID;
  629. return JS_TRUE;
  630. }
  631. *vp = JSVAL_VOID;
  632. if (js_LookupPropertyWithFlags(cx, proto, id, cx->resolveFlags,
  633. &obj2, &prop) < 0)
  634. return JS_FALSE;
  635. if (prop) {
  636. if (OBJ_IS_NATIVE(obj2)) {
  637. sprop = (JSScopeProperty *) prop;
  638. if (!js_NativeGet(cx, obj, obj2, sprop, vp))
  639. return JS_FALSE;
  640. }
  641. OBJ_DROP_PROPERTY(cx, obj2, prop);
  642. }
  643. return JS_TRUE;
  644. }
  645. *vp = obj->dslots[i];
  646. return JS_TRUE;
  647. }
  648. static JSBool
  649. slowarray_addProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
  650. {
  651. jsuint index, length;
  652. if (!js_IdIsIndex(id, &index))
  653. return JS_TRUE;
  654. length = obj->fslots[JSSLOT_ARRAY_LENGTH];
  655. if (index >= length)
  656. obj->fslots[JSSLOT_ARRAY_LENGTH] = index + 1;
  657. return JS_TRUE;
  658. }
  659. static void
  660. slowarray_trace(JSTracer *trc, JSObject *obj)
  661. {
  662. uint32 length = obj->fslots[JSSLOT_ARRAY_LENGTH];
  663. JS_ASSERT(STOBJ_GET_CLASS(obj) == &js_SlowArrayClass);
  664. /*
  665. * Move JSSLOT_ARRAY_LENGTH aside to prevent the GC from treating
  666. * untagged integer values as objects or strings.
  667. */
  668. obj->fslots[JSSLOT_ARRAY_LENGTH] = JSVAL_VOID;
  669. js_TraceObject(trc, obj);
  670. obj->fslots[JSSLOT_ARRAY_LENGTH] = length;
  671. }
  672. static JSObjectOps js_SlowArrayObjectOps;
  673. static JSObjectOps *
  674. slowarray_getObjectOps(JSContext *cx, JSClass *clasp)
  675. {
  676. return &js_SlowArrayObjectOps;
  677. }
  678. static JSBool
  679. array_setProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
  680. {
  681. uint32 i;
  682. if (id == ATOM_TO_JSID(cx->runtime->atomState.lengthAtom))
  683. return array_length_setter(cx, obj, id, vp);
  684. if (!OBJ_IS_DENSE_ARRAY(cx, obj))
  685. return js_SetProperty(cx, obj, id, vp);
  686. if (!js_IdIsIndex(id, &i) || INDEX_TOO_SPARSE(obj, i)) {
  687. if (!js_MakeArraySlow(cx, obj))
  688. return JS_FALSE;
  689. return js_SetProperty(cx, obj, id, vp);
  690. }
  691. if (!EnsureLength(cx, obj, i + 1))
  692. return JS_FALSE;
  693. if (i >= (uint32)obj->fslots[JSSLOT_ARRAY_LENGTH])
  694. obj->fslots[JSSLOT_ARRAY_LENGTH] = i + 1;
  695. if (obj->dslots[i] == JSVAL_HOLE)
  696. obj->fslots[JSSLOT_ARRAY_COUNT]++;
  697. obj->dslots[i] = *vp;
  698. return JS_TRUE;
  699. }
  700. #ifdef JS_TRACER
  701. JSBool FASTCALL
  702. js_Array_dense_setelem(JSContext* cx, JSObject* obj, jsint i, jsval v)
  703. {
  704. JS_ASSERT(OBJ_IS_DENSE_ARRAY(cx, obj));
  705. do {
  706. jsuint length = ARRAY_DENSE_LENGTH(obj);
  707. if ((jsuint)i < length) {
  708. if (obj->dslots[i] == JSVAL_HOLE) {
  709. if (cx->runtime->anyArrayProtoHasElement)
  710. break;
  711. if (i >= obj->fslots[JSSLOT_ARRAY_LENGTH])
  712. obj->fslots[JSSLOT_ARRAY_LENGTH] = i + 1;
  713. obj->fslots[JSSLOT_ARRAY_COUNT]++;
  714. }
  715. obj->dslots[i] = v;
  716. return JS_TRUE;
  717. }
  718. } while (0);
  719. return OBJ_SET_PROPERTY(cx, obj, INT_TO_JSID(i), &v);
  720. }
  721. #endif
  722. static JSBool
  723. array_defineProperty(JSContext *cx, JSObject *obj, jsid id, jsval value,
  724. JSPropertyOp getter, JSPropertyOp setter, uintN attrs,
  725. JSProperty **propp)
  726. {
  727. uint32 i;
  728. JSBool isIndex;
  729. if (id == ATOM_TO_JSID(cx->runtime->atomState.lengthAtom))
  730. return JS_TRUE;
  731. isIndex = js_IdIsIndex(ID_TO_VALUE(id), &i);
  732. if (!isIndex || attrs != JSPROP_ENUMERATE) {
  733. if (!ENSURE_SLOW_ARRAY(cx, obj))
  734. return JS_FALSE;
  735. if (isIndex && STOBJ_IS_DELEGATE(obj))
  736. cx->runtime->anyArrayProtoHasElement = JS_TRUE;
  737. return js_DefineProperty(cx, obj, id, value, getter, setter, attrs, propp);
  738. }
  739. return array_setProperty(cx, obj, id, &value);
  740. }
  741. static JSBool
  742. array_getAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop,
  743. uintN *attrsp)
  744. {
  745. *attrsp = id == ATOM_TO_JSID(cx->runtime->atomState.lengthAtom)
  746. ? JSPROP_PERMANENT : JSPROP_ENUMERATE;
  747. return JS_TRUE;
  748. }
  749. static JSBool
  750. array_setAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop,
  751. uintN *attrsp)
  752. {
  753. JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
  754. JSMSG_CANT_SET_ARRAY_ATTRS);
  755. return JS_FALSE;
  756. }
  757. static JSBool
  758. array_deleteProperty(JSContext *cx, JSObject *obj, jsval id, jsval *rval)
  759. {
  760. uint32 i;
  761. if (!OBJ_IS_DENSE_ARRAY(cx, obj))
  762. return js_DeleteProperty(cx, obj, id, rval);
  763. if (id == ATOM_TO_JSID(cx->runtime->atomState.lengthAtom)) {
  764. *rval = JSVAL_FALSE;
  765. return JS_TRUE;
  766. }
  767. if (js_IdIsIndex(id, &i) && i < ARRAY_DENSE_LENGTH(obj) &&
  768. obj->dslots[i] != JSVAL_HOLE) {
  769. obj->fslots[JSSLOT_ARRAY_COUNT]--;
  770. obj->dslots[i] = JSVAL_HOLE;
  771. }
  772. *rval = JSVAL_TRUE;
  773. return JS_TRUE;
  774. }
  775. /*
  776. * JSObjectOps.enumerate implementation.
  777. *
  778. * For a fast array, JSENUMERATE_INIT captures in the enumeration state both
  779. * the length of the array and the bitmap indicating the positions of holes in
  780. * the array. This ensures that adding or deleting array elements does not
  781. * affect the sequence of indexes JSENUMERATE_NEXT returns.
  782. *
  783. * For a common case of an array without holes, to represent the state we pack
  784. * the (nextEnumerationIndex, arrayLength) pair as a pseudo-boolean jsval.
  785. * This is possible when length <= PACKED_UINT_PAIR_BITS. For arrays with
  786. * greater length or holes we allocate the JSIndexIterState structure and
  787. * store it as an int-tagged private pointer jsval. For a slow array we
  788. * delegate the enumeration implementation to js_Enumerate in
  789. * slowarray_enumerate.
  790. *
  791. * Array mutations can turn a fast array into a slow one after the enumeration
  792. * starts. When this happens, slowarray_enumerate receives a state created
  793. * when the array was fast. To distinguish such fast state from a slow state,
  794. * which is an int-tagged pointer that js_Enumerate creates, we set not one
  795. * but two lowest bits when tagging a JSIndexIterState pointer -- see
  796. * INDEX_ITER_TAG usage below. Thus, when slowarray_enumerate receives a state
  797. * tagged with JSVAL_BOOLEAN or with two lowest bits set, it knows that this
  798. * is a fast state so it calls array_enumerate to continue enumerating the
  799. * indexes present in the original fast array.
  800. */
  801. #define PACKED_UINT_PAIR_BITS 14
  802. #define PACKED_UINT_PAIR_MASK JS_BITMASK(PACKED_UINT_PAIR_BITS)
  803. #define UINT_PAIR_TO_BOOLEAN_JSVAL(i,j) \
  804. (JS_ASSERT((uint32) (i) <= PACKED_UINT_PAIR_MASK), \
  805. JS_ASSERT((uint32) (j) <= PACKED_UINT_PAIR_MASK), \
  806. ((jsval) (i) << (PACKED_UINT_PAIR_BITS + JSVAL_TAGBITS)) | \
  807. ((jsval) (j) << (JSVAL_TAGBITS)) | \
  808. (jsval) JSVAL_BOOLEAN)
  809. #define BOOLEAN_JSVAL_TO_UINT_PAIR(v,i,j) \
  810. (JS_ASSERT(JSVAL_TAG(v) == JSVAL_BOOLEAN), \
  811. (i) = (uint32) ((v) >> (PACKED_UINT_PAIR_BITS + JSVAL_TAGBITS)), \
  812. (j) = (uint32) ((v) >> JSVAL_TAGBITS) & PACKED_UINT_PAIR_MASK, \
  813. JS_ASSERT((i) <= PACKED_UINT_PAIR_MASK))
  814. JS_STATIC_ASSERT(PACKED_UINT_PAIR_BITS * 2 + JSVAL_TAGBITS <= JS_BITS_PER_WORD);
  815. typedef struct JSIndexIterState {
  816. uint32 index;
  817. uint32 length;
  818. JSBool hasHoles;
  819. /*
  820. * Variable-length bitmap representing array's holes. It must not be
  821. * accessed when hasHoles is false.
  822. */
  823. jsbitmap holes[1];
  824. } JSIndexIterState;
  825. #define INDEX_ITER_TAG 3
  826. JS_STATIC_ASSERT(JSVAL_INT == 1);
  827. static JSBool
  828. array_enumerate(JSContext *cx, JSObject *obj, JSIterateOp enum_op,
  829. jsval *statep, jsid *idp)
  830. {
  831. uint32 length, i;
  832. JSIndexIterState *ii;
  833. switch (enum_op) {
  834. case JSENUMERATE_INIT:
  835. JS_ASSERT(OBJ_IS_DENSE_ARRAY(cx, obj));
  836. length = ARRAY_DENSE_LENGTH(obj);
  837. if (idp)
  838. *idp = INT_TO_JSVAL(obj->fslots[JSSLOT_ARRAY_COUNT]);
  839. ii = NULL;
  840. for (i = 0; i != length; ++i) {
  841. if (obj->dslots[i] == JSVAL_HOLE) {
  842. if (!ii) {
  843. ii = (JSIndexIterState *)
  844. JS_malloc(cx, offsetof(JSIndexIterState, holes) +
  845. JS_BITMAP_SIZE(length));
  846. if (!ii)
  847. return JS_FALSE;
  848. ii->hasHoles = JS_TRUE;
  849. memset(ii->holes, 0, JS_BITMAP_SIZE(length));
  850. }
  851. JS_SET_BIT(ii->holes, i);
  852. }
  853. }
  854. if (!ii) {
  855. /* Array has no holes. */
  856. if (length <= PACKED_UINT_PAIR_MASK) {
  857. *statep = UINT_PAIR_TO_BOOLEAN_JSVAL(0, length);
  858. break;
  859. }
  860. ii = (JSIndexIterState *)
  861. JS_malloc(cx, offsetof(JSIndexIterState, holes));
  862. if (!ii)
  863. return JS_FALSE;
  864. ii->hasHoles = JS_FALSE;
  865. }
  866. ii->index = 0;
  867. ii->length = length;
  868. *statep = (jsval) ii | INDEX_ITER_TAG;
  869. JS_ASSERT(*statep & JSVAL_INT);
  870. break;
  871. case JSENUMERATE_NEXT:
  872. if (JSVAL_TAG(*statep) == JSVAL_BOOLEAN) {
  873. BOOLEAN_JSVAL_TO_UINT_PAIR(*statep, i, length);
  874. if (i != length) {
  875. *idp = INT_TO_JSID(i);
  876. *statep = UINT_PAIR_TO_BOOLEAN_JSVAL(i + 1, length);
  877. break;
  878. }
  879. } else {
  880. JS_ASSERT((*statep & INDEX_ITER_TAG) == INDEX_ITER_TAG);
  881. ii = (JSIndexIterState *) (*statep & ~INDEX_ITER_TAG);
  882. i = ii->index;
  883. if (i != ii->length) {
  884. /* Skip holes if any. */
  885. if (ii->hasHoles) {
  886. while (JS_TEST_BIT(ii->holes, i) && ++i != ii->length)
  887. continue;
  888. }
  889. if (i != ii->length) {
  890. ii->index = i + 1;
  891. return js_IndexToId(cx, i, idp);
  892. }
  893. }
  894. }
  895. /* FALL THROUGH */
  896. case JSENUMERATE_DESTROY:
  897. if (JSVAL_TAG(*statep) != JSVAL_BOOLEAN) {
  898. JS_ASSERT((*statep & INDEX_ITER_TAG) == INDEX_ITER_TAG);
  899. ii = (JSIndexIterState *) (*statep & ~INDEX_ITER_TAG);
  900. JS_free(cx, ii);
  901. }
  902. *statep = JSVAL_NULL;
  903. break;
  904. }
  905. return JS_TRUE;
  906. }
  907. static JSBool
  908. slowarray_enumerate(JSContext *cx, JSObject *obj, JSIterateOp enum_op,
  909. jsval *statep, jsid *idp)
  910. {
  911. JSBool ok;
  912. /* Are we continuing an enumeration that started when we were dense? */
  913. if (enum_op != JSENUMERATE_INIT) {
  914. if (JSVAL_TAG(*statep) == JSVAL_BOOLEAN ||
  915. (*statep & INDEX_ITER_TAG) == INDEX_ITER_TAG) {
  916. return array_enumerate(cx, obj, enum_op, statep, idp);
  917. }
  918. JS_ASSERT((*statep & INDEX_ITER_TAG) == JSVAL_INT);
  919. }
  920. ok = js_Enumerate(cx, obj, enum_op, statep, idp);
  921. JS_ASSERT(*statep == JSVAL_NULL || (*statep & INDEX_ITER_TAG) == JSVAL_INT);
  922. return ok;
  923. }
  924. static void
  925. array_finalize(JSContext *cx, JSObject *obj)
  926. {
  927. if (obj->dslots)
  928. JS_free(cx, obj->dslots - 1);
  929. obj->dslots = NULL;
  930. }
  931. static void
  932. array_trace(JSTracer *trc, JSObject *obj)
  933. {
  934. uint32 length;
  935. size_t i;
  936. jsval v;
  937. JS_ASSERT(OBJ_IS_DENSE_ARRAY(cx, obj));
  938. length = ARRAY_DENSE_LENGTH(obj);
  939. for (i = 0; i < length; i++) {
  940. v = obj->dslots[i];
  941. if (JSVAL_IS_TRACEABLE(v)) {
  942. JS_SET_TRACING_INDEX(trc, "array_dslots", i);
  943. JS_CallTracer(trc, JSVAL_TO_TRACEABLE(v), JSVAL_TRACE_KIND(v));
  944. }
  945. }
  946. for (i = JSSLOT_PROTO; i <= JSSLOT_PARENT; ++i) {
  947. v = STOBJ_GET_SLOT(obj, i);
  948. if (JSVAL_IS_TRACEABLE(v)) {
  949. JS_SET_TRACING_DETAILS(trc, js_PrintObjectSlotName, obj, i);
  950. JS_CallTracer(trc, JSVAL_TO_TRACEABLE(v), JSVAL_TRACE_KIND(v));
  951. }
  952. }
  953. }
  954. static JSObjectMap *
  955. array_newObjectMap(JSContext *cx, jsrefcount nrefs, JSObjectOps *ops,
  956. JSClass *clasp, JSObject *obj)
  957. {
  958. #ifdef DEBUG
  959. extern JSClass js_ArrayClass;
  960. extern JSObjectOps js_ArrayObjectOps;
  961. #endif
  962. JSObjectMap *map = (JSObjectMap *) JS_malloc(cx, sizeof(*map));
  963. if (!map)
  964. return NULL;
  965. map->nrefs = nrefs;
  966. JS_ASSERT(ops == &js_ArrayObjectOps);
  967. map->ops = ops;
  968. JS_ASSERT(clasp == &js_ArrayClass);
  969. map->freeslot = JSSLOT_FREE(clasp);
  970. return map;
  971. }
  972. void
  973. array_destroyObjectMap(JSContext *cx, JSObjectMap *map)
  974. {
  975. JS_free(cx, map);
  976. }
  977. JSObjectOps js_ArrayObjectOps = {
  978. array_newObjectMap, array_destroyObjectMap,
  979. array_lookupProperty, array_defineProperty,
  980. array_getProperty, array_setProperty,
  981. array_getAttributes, array_setAttributes,
  982. array_deleteProperty, js_DefaultValue,
  983. array_enumerate, js_CheckAccess,
  984. NULL, array_dropProperty,
  985. NULL, NULL,
  986. NULL, js_HasInstance,
  987. js_SetProtoOrParent, js_SetProtoOrParent,
  988. array_trace, NULL,
  989. NULL, NULL
  990. };
  991. static JSObjectOps *
  992. array_getObjectOps(JSContext *cx, JSClass *clasp)
  993. {
  994. return &js_ArrayObjectOps;
  995. }
  996. JSClass js_ArrayClass = {
  997. "Array",
  998. JSCLASS_HAS_PRIVATE | JSCLASS_HAS_CACHED_PROTO(JSProto_Array) |
  999. JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_NEW_ENUMERATE,
  1000. JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
  1001. JS_EnumerateStub, JS_ResolveStub, js_TryValueOf, array_finalize,
  1002. array_getObjectOps, NULL, NULL, NULL,
  1003. NULL, NULL, NULL, NULL
  1004. };
  1005. JSClass js_SlowArrayClass = {
  1006. "Array",
  1007. JSCLASS_HAS_PRIVATE | JSCLASS_HAS_CACHED_PROTO(JSProto_Array),
  1008. slowarray_addProperty, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
  1009. JS_EnumerateStub, JS_ResolveStub, js_TryValueOf, JS_FinalizeStub,
  1010. slowarray_getObjectOps, NULL, NULL, NULL,
  1011. NULL, NULL, NULL, NULL
  1012. };
  1013. /*
  1014. * Convert an array object from fast-and-dense to slow-and-flexible.
  1015. */
  1016. JSBool
  1017. js_MakeArraySlow(JSContext *cx, JSObject *obj)
  1018. {
  1019. JSObjectMap *map, *oldmap;
  1020. uint32 i, length;
  1021. JS_ASSERT(OBJ_GET_CLASS(cx, obj) == &js_ArrayClass);
  1022. /* Create a native scope. */
  1023. map = js_NewObjectMap(cx, obj->map->nrefs, &js_SlowArrayObjectOps,
  1024. &js_SlowArrayClass, obj);
  1025. if (!map)
  1026. return JS_FALSE;
  1027. length = ARRAY_DENSE_LENGTH(obj);
  1028. if (length) {
  1029. map->freeslot = STOBJ_NSLOTS(obj) + JS_INITIAL_NSLOTS;
  1030. obj->dslots[-1] = JS_INITIAL_NSLOTS + length;
  1031. } else {
  1032. map->freeslot = STOBJ_NSLOTS(obj);
  1033. }
  1034. /* Create new properties pointing to existing values in dslots */
  1035. for (i = 0; i < length; i++) {
  1036. jsid id;
  1037. JSScopeProperty *sprop;
  1038. if (!JS_ValueToId(cx, INT_TO_JSVAL(i), &id))
  1039. goto out_bad;
  1040. if (obj->dslots[i] == JSVAL_HOLE) {
  1041. obj->dslots[i] = JSVAL_VOID;
  1042. continue;
  1043. }
  1044. sprop = js_AddScopeProperty(cx, (JSScope *)map, id, NULL, NULL,
  1045. i + JS_INITIAL_NSLOTS, JSPROP_ENUMERATE,
  1046. 0, 0);
  1047. if (!sprop)
  1048. goto out_bad;
  1049. }
  1050. /*
  1051. * Render our formerly-reserved count property GC-safe. If length fits in
  1052. * a jsval, set our slow/sparse COUNT to the current length as a jsval, so
  1053. * we can tell when only named properties have been added to a dense array
  1054. * to make it slow-but-not-sparse.
  1055. */
  1056. length = obj->fslots[JSSLOT_ARRAY_LENGTH];
  1057. obj->fslots[JSSLOT_ARRAY_COUNT] = INT_FITS_IN_JSVAL(length)
  1058. ? INT_TO_JSVAL(length)
  1059. : JSVAL_VOID;
  1060. /* Make sure we preserve any flags borrowing bits in classword. */
  1061. obj->classword ^= (jsuword) &js_ArrayClass;
  1062. obj->classword |= (jsuword) &js_SlowArrayClass;
  1063. /* Swap in our new map. */
  1064. oldmap = obj->map;
  1065. obj->map = map;
  1066. array_destroyObjectMap(cx, oldmap);
  1067. return JS_TRUE;
  1068. out_bad:
  1069. js_DestroyObjectMap(cx, map);
  1070. return JS_FALSE;
  1071. }
  1072. enum ArrayToStringOp {
  1073. TO_STRING,
  1074. TO_LOCALE_STRING,
  1075. TO_SOURCE
  1076. };
  1077. /*
  1078. * When op is TO_STRING or TO_LOCALE_STRING sep indicates a separator to use
  1079. * or "," when sep is NULL.
  1080. * When op is TO_SOURCE sep must be NULL.
  1081. */
  1082. static JSBool
  1083. array_join_sub(JSContext *cx, JSObject *obj, enum ArrayToStringOp op,
  1084. JSString *sep, jsval *rval)
  1085. {
  1086. JSBool ok, hole;
  1087. jsuint length, index;
  1088. jschar *chars, *ochars;
  1089. size_t nchars, growth, seplen, tmplen, extratail;
  1090. const jschar *sepstr;
  1091. JSString *str;
  1092. JSHashEntry *he;
  1093. JSAtom *atom;
  1094. JS_CHECK_RECURSION(cx, return JS_FALSE);
  1095. ok = js_GetLengthProperty(cx, obj, &length);
  1096. if (!ok)
  1097. return JS_FALSE;
  1098. he = js_EnterSharpObject(cx, obj, NULL, &chars);
  1099. if (!he)
  1100. return JS_FALSE;
  1101. #ifdef DEBUG
  1102. growth = (size_t) -1;
  1103. #endif
  1104. if (op == TO_SOURCE) {
  1105. if (IS_SHARP(he)) {
  1106. #if JS_HAS_SHARP_VARS
  1107. nchars = js_strlen(chars);
  1108. #else
  1109. chars[0] = '[';
  1110. chars[1] = ']';
  1111. chars[2] = 0;
  1112. nchars = 2;
  1113. #endif
  1114. goto make_string;
  1115. }
  1116. /*
  1117. * Always allocate 2 extra chars for closing ']' and terminating 0
  1118. * and then preallocate 1 + extratail to include starting '['.
  1119. */
  1120. extratail = 2;
  1121. growth = (1 + extratail) * sizeof(jschar);
  1122. if (!chars) {
  1123. nchars = 0;
  1124. chars = (jschar *) malloc(growth);
  1125. if (!chars)
  1126. goto done;
  1127. } else {
  1128. MAKE_SHARP(he);
  1129. nchars = js_strlen(chars);
  1130. growth += nchars * sizeof(jschar);
  1131. chars = (jschar *)realloc((ochars = chars), growth);
  1132. if (!chars) {
  1133. free(ochars);
  1134. goto done;
  1135. }
  1136. }
  1137. chars[nchars++] = '[';
  1138. JS_ASSERT(sep == NULL);
  1139. sepstr = NULL; /* indicates to use ", " as separator */
  1140. seplen = 2;
  1141. } else {
  1142. /*
  1143. * Free any sharp variable definition in chars. Normally, we would
  1144. * MAKE_SHARP(he) so that only the first sharp variable annotation is
  1145. * a definition, and all the rest are references, but in the current
  1146. * case of (op != TO_SOURCE), we don't need chars at all.
  1147. */
  1148. if (chars)
  1149. JS_free(cx, chars);
  1150. chars = NULL;
  1151. nchars = 0;
  1152. extratail = 1; /* allocate extra char for terminating 0 */
  1153. /* Return the empty string on a cycle as well as on empty join. */
  1154. if (IS_BUSY(he) || length == 0) {
  1155. js_LeaveSharpObject(cx, NULL);
  1156. *rval = JS_GetEmptyStringValue(cx);
  1157. return ok;
  1158. }
  1159. /* Flag he as BUSY so we can distinguish a cycle from a join-point. */
  1160. MAKE_BUSY(he);
  1161. if (sep) {
  1162. JSSTRING_CHARS_AND_LENGTH(sep, sepstr, seplen);
  1163. } else {
  1164. sepstr = NULL; /* indicates to use "," as separator */
  1165. seplen = 1;
  1166. }
  1167. }
  1168. /* Use rval to locally root each element value as we loop and convert. */
  1169. for (index = 0; index < length; index++) {
  1170. ok = (JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) &&
  1171. GetArrayElement(cx, obj, index, &hole, rval));
  1172. if (!ok)
  1173. goto done;
  1174. if (hole ||
  1175. (op != TO_SOURCE &&
  1176. (JSVAL_IS_VOID(*rval) || JSVAL_IS_NULL(*rval)))) {
  1177. str = cx->runtime->emptyString;
  1178. } else {
  1179. if (op == TO_LOCALE_STRING) {
  1180. JSObject *robj;
  1181. atom = cx->runtime->atomState.toLocaleStringAtom;
  1182. ok = js_ValueToObject(cx, *rval, &robj);
  1183. if (ok) {
  1184. /* Re-use *rval to protect robj temporarily. */
  1185. *rval = OBJECT_TO_JSVAL(robj);
  1186. ok = js_TryMethod(cx, robj, atom, 0, NULL, rval);
  1187. }
  1188. if (!ok)
  1189. goto done;
  1190. str = js_ValueToString(cx, *rval);
  1191. } else if (op == TO_STRING) {
  1192. str = js_ValueToString(cx, *rval);
  1193. } else {
  1194. JS_ASSERT(op == TO_SOURCE);
  1195. str = js_ValueToSource(cx, *rval);
  1196. }
  1197. if (!str) {
  1198. ok = JS_FALSE;
  1199. goto done;
  1200. }
  1201. }
  1202. /*
  1203. * Do not append separator after the last element unless it is a hole
  1204. * and we are in toSource. In that case we append single ",".
  1205. */
  1206. if (index + 1 == length)
  1207. seplen = (hole && op == TO_SOURCE) ? 1 : 0;
  1208. /* Allocate 1 at end for closing bracket and zero. */
  1209. tmplen = JSSTRING_LENGTH(str);
  1210. growth = nchars + tmplen + seplen + extratail;
  1211. if (nchars > growth || tmplen > growth ||
  1212. growth > (size_t)-1 / sizeof(jschar)) {
  1213. if (chars) {
  1214. free(chars);
  1215. chars = NULL;
  1216. }
  1217. goto done;
  1218. }
  1219. growth *= sizeof(jschar);
  1220. JS_COUNT_OPERATION(cx, JSOW_ALLOCATION);
  1221. if (!chars) {
  1222. chars = (jschar *) malloc(growth);
  1223. if (!chars)
  1224. goto done;
  1225. } else {
  1226. chars = (jschar *) realloc((ochars = chars), growth);
  1227. if (!chars) {
  1228. free(ochars);
  1229. goto done;
  1230. }
  1231. }
  1232. js_strncpy(&chars[nchars], JSSTRING_CHARS(str), tmplen);
  1233. nchars += tmplen;
  1234. if (seplen) {
  1235. if (sepstr) {
  1236. js_strncpy(&chars[nchars], sepstr, seplen);
  1237. } else {
  1238. JS_ASSERT(seplen == 1 || seplen == 2);
  1239. chars[nchars] = ',';
  1240. if (seplen == 2)
  1241. chars[nchars + 1] = ' ';
  1242. }
  1243. nchars += seplen;
  1244. }
  1245. }
  1246. done:
  1247. if (op == TO_SOURCE) {
  1248. if (chars)
  1249. chars[nchars++] = ']';
  1250. } else {
  1251. CLEAR_BUSY(he);
  1252. }
  1253. js_LeaveSharpObject(cx, NULL);
  1254. if (!ok) {
  1255. if (chars)
  1256. free(chars);
  1257. return ok;
  1258. }
  1259. make_string:
  1260. if (!chars) {
  1261. JS_ReportOutOfMemory(cx);
  1262. return JS_FALSE;
  1263. }
  1264. chars[nchars] = 0;
  1265. JS_ASSERT(growth == (size_t)-1 || (nchars + 1) * sizeof(jschar) == growth);
  1266. str = js_NewString(cx, chars, nchars);
  1267. if (!str) {
  1268. free(chars);
  1269. return JS_FALSE;
  1270. }
  1271. *rval = STRING_TO_JSVAL(str);
  1272. return JS_TRUE;
  1273. }
  1274. #if JS_HAS_TOSOURCE
  1275. static JSBool
  1276. array_toSource(JSContext *cx, uintN argc, jsval *vp)
  1277. {
  1278. JSObject *obj;
  1279. obj = JS_THIS_OBJECT(cx, vp);
  1280. if (OBJ_GET_CLASS(cx, obj) != &js_SlowArrayClass &&
  1281. !JS_InstanceOf(cx, obj, &js_ArrayClass, vp + 2)) {
  1282. return JS_FALSE;
  1283. }
  1284. return array_join_sub(cx, obj, TO_SOURCE, NULL, vp);
  1285. }
  1286. #endif
  1287. static JSBool
  1288. array_toString(JSContext *cx, uintN argc, jsval *vp)
  1289. {
  1290. JSObject *obj;
  1291. obj = JS_THIS_OBJECT(cx, vp);
  1292. if (OBJ_GET_CLASS(cx, obj) != &js_SlowArrayClass &&
  1293. !JS_InstanceOf(cx, obj, &js_ArrayClass, vp + 2)) {
  1294. return JS_FALSE;
  1295. }
  1296. return array_join_sub(cx, obj, TO_STRING, NULL, vp);
  1297. }
  1298. static JSBool
  1299. array_toLocaleString(JSContext *cx, uintN argc, jsval *vp)
  1300. {
  1301. JSObject *obj;
  1302. obj = JS_THIS_OBJECT(cx, vp);
  1303. if (OBJ_GET_CLASS(cx, obj) != &js_SlowArrayClass &&
  1304. !JS_InstanceOf(cx, obj, &js_ArrayClass, vp + 2)) {
  1305. return JS_FALSE;
  1306. }
  1307. /*
  1308. * Passing comma here as the separator. Need a way to get a
  1309. * locale-specific version.
  1310. */
  1311. return array_join_sub(cx, obj, TO_LOCALE_STRING, NULL, vp);
  1312. }
  1313. static JSBool
  1314. InitArrayElements(JSContext *cx, JSObject *obj, jsuint start, jsuint end,
  1315. jsval *vector)
  1316. {
  1317. if (OBJ_IS_DENSE_ARRAY(cx, obj)) {
  1318. if (!EnsureLength(cx, obj, end))
  1319. return JS_FALSE;
  1320. if (end > (uint32)obj->fslots[JSSLOT_ARRAY_LENGTH])
  1321. obj->fslots[JSSLOT_ARRAY_LENGTH] = end;
  1322. memcpy(obj->dslots + start, vector, sizeof(jsval) * (end - start));
  1323. return JS_TRUE;
  1324. }
  1325. while (start != end) {
  1326. if (!JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) ||
  1327. !SetArrayElement(cx, obj, start++, *vector++)) {
  1328. return JS_FALSE;
  1329. }
  1330. }
  1331. return JS_TRUE;
  1332. }
  1333. static JSBool
  1334. InitArrayObject(JSContext *cx, JSObject *obj, jsuint length, jsval *vector,
  1335. JSBool holey = JS_FALSE)
  1336. {
  1337. JS_ASSERT(OBJ_IS_ARRAY(cx, obj));
  1338. obj->fslots[JSSLOT_ARRAY_LENGTH] = length;
  1339. if (vector) {
  1340. if (!EnsureLength(cx, obj, length))
  1341. return JS_FALSE;
  1342. jsuint count = length;
  1343. if (!holey) {
  1344. memcpy(obj->dslots, vector, length * sizeof (jsval));
  1345. } else {
  1346. for (jsuint i = 0; i < length; i++) {
  1347. if (vector[i] == JSVAL_HOLE)
  1348. --count;
  1349. obj->dslots[i] = vector[i];
  1350. }
  1351. }
  1352. obj->fslots[JSSLOT_ARRAY_COUNT] = count;
  1353. } else {
  1354. obj->fslots[JSSLOT_ARRAY_COUNT] = 0;
  1355. }
  1356. return JS_TRUE;
  1357. }
  1358. #ifdef JS_TRACER
  1359. static JSString* FASTCALL
  1360. Array_p_join(JSContext* cx, JSObject* obj, JSString *str)
  1361. {
  1362. jsval v;
  1363. if (!array_join_sub(cx, obj, TO_STRING, str, &v))
  1364. return NULL;
  1365. JS_ASSERT(JSVAL_IS_STRING(v));
  1366. return JSVAL_TO_STRING(v);
  1367. }
  1368. static JSString* FASTCALL
  1369. Array_p_toString(JSContext* cx, JSObject* obj)
  1370. {
  1371. jsval v;
  1372. if (!array_join_sub(cx, obj, TO_STRING, NULL, &v))
  1373. return NULL;
  1374. JS_ASSERT(JSVAL_IS_STRING(v));
  1375. return JSVAL_TO_STRING(v);
  1376. }
  1377. #endif
  1378. /*
  1379. * Perl-inspired join, reverse, and sort.
  1380. */
  1381. static JSBool
  1382. array_join(JSContext *cx, uintN argc, jsval *vp)
  1383. {
  1384. JSString *str;
  1385. JSObject *obj;
  1386. if (argc == 0 || JSVAL_IS_VOID(vp[2])) {
  1387. str = NULL;
  1388. } else {
  1389. str = js_ValueToString(cx, vp[2]);
  1390. if (!str)
  1391. return JS_FALSE;
  1392. vp[2] = STRING_TO_JSVAL(str);
  1393. }
  1394. obj = JS_THIS_OBJECT(cx, vp);
  1395. return obj && array_join_sub(cx, obj, TO_STRING, str, vp);
  1396. }
  1397. static JSBool
  1398. array_reverse(JSContext *cx, uintN argc, jsval *vp)
  1399. {
  1400. JSObject *obj;
  1401. JSTempValueRooter tvr;
  1402. jsuint len, half, i;
  1403. JSBool ok, hole, hole2;
  1404. obj = JS_THIS_OBJECT(cx, vp);
  1405. if (!obj || !js_GetLengthProperty(cx, obj, &len))
  1406. return JS_FALSE;
  1407. ok = JS_TRUE;
  1408. JS_PUSH_SINGLE_TEMP_ROOT(cx, JSVAL_NULL, &tvr);
  1409. half = len / 2;
  1410. for (i = 0; i < half; i++) {
  1411. ok = JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) &&
  1412. GetArrayElement(cx, obj, i, &hole, &tvr.u.value) &&
  1413. GetArrayElement(cx, obj, len - i - 1, &hole2, vp) &&
  1414. SetOrDeleteArrayElement(cx, obj, len - i - 1, hole, tvr.u.value) &&
  1415. SetOrDeleteArrayElement(cx, obj, i, hole2, *vp);
  1416. if (!ok)
  1417. break;
  1418. }
  1419. JS_POP_TEMP_ROOT(cx, &tvr);
  1420. *vp = OBJECT_TO_JSVAL(obj);
  1421. return ok;
  1422. }
  1423. typedef struct MSortArgs {
  1424. size_t elsize;
  1425. JSComparator cmp;
  1426. void *arg;
  1427. JSBool fastcopy;
  1428. } MSortArgs;
  1429. /* Helper function for js_MergeSort. */
  1430. static JSBool
  1431. MergeArrays(MSortArgs *msa, void *src, void *dest, size_t run1, size_t run2)
  1432. {
  1433. void *arg, *a, *b, *c;
  1434. size_t elsize, runtotal;
  1435. int cmp_result;
  1436. JSComparator cmp;
  1437. JSBool fastcopy;
  1438. runtotal = run1 + run2;
  1439. elsize = msa->elsize;
  1440. cmp = msa->cmp;
  1441. arg = msa->arg;
  1442. fastcopy = msa->fastcopy;
  1443. #define CALL_CMP(a, b) \
  1444. if (!cmp(arg, (a), (b), &cmp_result)) return JS_FALSE;
  1445. /* Copy runs already in sorted order. */
  1446. b = (char *)src + run1 * elsize;
  1447. a = (char *)b - elsize;
  1448. CALL_CMP(a, b);
  1449. if (cmp_result <= 0) {
  1450. memcpy(dest, src, runtotal * elsize);
  1451. return JS_TRUE;
  1452. }
  1453. #define COPY_ONE(p,q,n) \
  1454. (fastcopy ? (void)(*(jsval*)(p) = *(jsval*)(q)) : (void)memcpy(p, q, n))
  1455. a = src;
  1456. c = dest;
  1457. for (; runtotal != 0; runtotal--) {
  1458. JSBool from_a = run2 == 0;
  1459. if (!from_a && run1 != 0) {
  1460. CALL_CMP(a,b);
  1461. from_a = cmp_result <= 0;
  1462. }
  1463. if (from_a) {
  1464. COPY_ONE(c, a, elsize);
  1465. run1--;
  1466. a = (char *)a + elsize;
  1467. } else {
  1468. COPY_ONE(c, b, elsize);
  1469. run2--;
  1470. b = (char *)b + elsize;
  1471. }
  1472. c = (char *)c + elsize;
  1473. }
  1474. #undef COPY_ONE
  1475. #undef CALL_CMP
  1476. return JS_TRUE;
  1477. }
  1478. /*
  1479. * This sort is stable, i.e. sequence of equal elements is preserved.
  1480. * See also bug #224128.
  1481. */
  1482. JSBool
  1483. js_MergeSort(void *src, size_t nel, size_t elsize,
  1484. JSComparator cmp, void *arg, void *tmp)
  1485. {
  1486. void *swap, *vec1, *vec2;
  1487. MSortArgs msa;
  1488. size_t i, j, lo, hi, run;
  1489. JSBool fastcopy;
  1490. int cmp_result;
  1491. /* Avoid memcpy overhead for word-sized and word-aligned elements. */
  1492. fastcopy = (elsize == sizeof(jsval) &&
  1493. (((jsuword) src | (jsuword) tmp) & JSVAL_ALIGN) == 0);
  1494. #define COPY_ONE(p,q,n) \
  1495. (fastcopy ? (void)(*(jsval*)(p) = *(jsval*)(q)) : (void)memcpy(p, q, n))
  1496. #define CALL_CMP(a, b) \
  1497. if (!cmp(arg, (a), (b), &cmp_result)) return JS_FALSE;
  1498. #define INS_SORT_INT 4
  1499. /*
  1500. * Apply insertion sort to small chunks to reduce the number of merge
  1501. * passes needed.
  1502. */
  1503. for (lo = 0; lo < nel; lo += INS_SORT_INT) {
  1504. hi = lo + INS_SORT_INT;
  1505. if (hi >= nel)
  1506. hi = nel;
  1507. for (i = lo + 1; i < hi; i++) {
  1508. vec1 = (char *)src + i * elsize;
  1509. vec2 = (char *)vec1 - elsize;
  1510. for (j = i; j > lo; j--) {
  1511. CALL_CMP(vec2, vec1);
  1512. /* "<=" instead of "<" insures the sort is stable */
  1513. if (cmp_result <= 0) {
  1514. break;
  1515. }
  1516. /* Swap elements, using "tmp" as tmp storage */
  1517. COPY_ONE(tmp, vec2, elsize);
  1518. COPY_ONE(vec2, vec1, elsize);
  1519. COPY_ONE(vec1, tmp, elsize);
  1520. vec1 = vec2;
  1521. vec2 = (char *)vec1 - elsize;
  1522. }
  1523. }
  1524. }
  1525. #undef CALL_CMP
  1526. #undef COPY_ONE
  1527. msa.elsize = elsize;
  1528. msa.cmp = cmp;
  1529. msa.arg = arg;
  1530. msa.fastcopy = fastcopy;
  1531. vec1 = src;
  1532. vec2 = tmp;
  1533. for (run = INS_SORT_INT; run < nel; run *= 2) {
  1534. for (lo = 0; lo < nel; lo += 2 * run) {
  1535. hi = lo + run;
  1536. if (hi >= nel) {
  1537. memcpy((char *)vec2 + lo * elsize, (char *)vec1 + lo * elsize,
  1538. (nel - lo) * elsize);
  1539. break;
  1540. }
  1541. if (!MergeArrays(&msa, (char *)vec1 + lo * elsize,
  1542. (char *)vec2 + lo * elsize, run,
  1543. hi + run > nel ? nel - hi : run)) {
  1544. return JS_FALSE;
  1545. }
  1546. }
  1547. swap = vec1;
  1548. vec1 = vec2;
  1549. vec2 = swap;
  1550. }
  1551. if (src != vec1)
  1552. memcpy(src, tmp, nel * elsize);
  1553. return JS_TRUE;
  1554. }
  1555. typedef struct CompareArgs {
  1556. JSContext *context;
  1557. jsval fval;
  1558. jsval *elemroot; /* stack needed for js_Invoke */
  1559. } CompareArgs;
  1560. static JSBool
  1561. sort_compare(void *arg, const void *a, const void *b, int *result)
  1562. {
  1563. jsval av = *(const jsval *)a, bv = *(const jsval *)b;
  1564. CompareArgs *ca = (CompareArgs *) arg;
  1565. JSContext *cx = ca->context;
  1566. jsval *invokevp, *sp;
  1567. jsdouble cmp;
  1568. /**
  1569. * array_sort deals with holes and undefs on its own and they should not
  1570. * come here.
  1571. */
  1572. JS_ASSERT(!JSVAL_IS_VOID(av));
  1573. JS_ASSERT(!JSVAL_IS_VOID(bv));
  1574. if (!JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP))
  1575. return JS_FALSE;
  1576. invokevp = ca->elemroot;
  1577. sp = invokevp;
  1578. *sp++ = ca->fval;
  1579. *sp++ = JSVAL_NULL;
  1580. *sp++ = av;
  1581. *sp++ = bv;
  1582. if (!js_Invoke(cx, 2, invokevp, 0))
  1583. return JS_FALSE;
  1584. cmp = js_ValueToNumber(cx, invokevp);
  1585. if (JSVAL_IS_NULL(*invokevp))
  1586. return JS_FALSE;
  1587. /* Clamp cmp to -1, 0, 1. */
  1588. *result = 0;
  1589. if (!JSDOUBLE_IS_NaN(cmp) && cmp != 0)
  1590. *result = cmp > 0 ? 1 : -1;
  1591. /*
  1592. * XXX else report some kind of error here? ECMA talks about 'consistent
  1593. * compare functions' that don't return NaN, but is silent about what the
  1594. * result should be. So we currently ignore it.
  1595. */
  1596. return JS_TRUE;
  1597. }
  1598. static int
  1599. sort_compare_strings(void *arg, const void *a, const void *b, int *result)
  1600. {
  1601. jsval av = *(const jsval *)a, bv = *(const jsval *)b;
  1602. JS_ASSERT(JSVAL_IS_STRING(av));
  1603. JS_ASSERT(JSVAL_IS_STRING(bv));
  1604. if (!JS_CHECK_OPERATION_LIMIT((JSContext *)arg, JSOW_JUMP))
  1605. return JS_FALSE;
  1606. *result = (int) js_CompareStrings(JSVAL_TO_STRING(av), JSVAL_TO_STRING(bv));
  1607. return JS_TRUE;
  1608. }
  1609. /*
  1610. * The array_sort function below assumes JSVAL_NULL is zero in order to
  1611. * perform initialization using memset. Other parts of SpiderMonkey likewise
  1612. * "know" that JSVAL_NULL is zero; this static assertion covers all cases.
  1613. */
  1614. JS_STATIC_ASSERT(JSVAL_NULL == 0);
  1615. static JSBool
  1616. array_sort(JSContext *cx, uintN argc, jsval *vp)
  1617. {
  1618. jsval *argv, fval, *vec, *mergesort_tmp, v;
  1619. JSObject *obj;
  1620. CompareArgs ca;
  1621. jsuint len, newlen, i, undefs;
  1622. JSTempValueRooter tvr;
  1623. JSBool hole;
  1624. bool ok;
  1625. size_t elemsize;
  1626. JSString *str;
  1627. /*
  1628. * Optimize the default compare function case if all of obj's elements
  1629. * have values of type string.
  1630. */
  1631. JSBool all_strings;
  1632. argv = JS_ARGV(cx, vp);
  1633. if (argc > 0) {
  1634. if (JSVAL_IS_PRIMITIVE(argv[0])) {
  1635. JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
  1636. JSMSG_BAD_SORT_ARG);
  1637. return JS_FALSE;
  1638. }
  1639. fval = argv[0]; /* non-default compare function */
  1640. } else {
  1641. fval = JSVAL_NULL;
  1642. }
  1643. obj = JS_THIS_OBJECT(cx, vp);
  1644. if (!obj || !js_GetLengthProperty(cx, obj, &len))
  1645. return JS_FALSE;
  1646. if (len == 0) {
  1647. *vp = OBJECT_TO_JSVAL(obj);
  1648. return JS_TRUE;
  1649. }
  1650. /*
  1651. * We need a temporary array of 2 * len jsvals to hold the array elements
  1652. * and the scratch space for merge sort. Check that its size does not
  1653. * overflow size_t, which would allow for indexing beyond the end of the
  1654. * malloc'd vector.
  1655. */
  1656. #if JS_BITS_PER_WORD == 32
  1657. if ((size_t)len > ~(size_t)0 / (2 * sizeof(jsval))) {
  1658. js_ReportAllocationOverflow(cx);
  1659. return JS_FALSE;
  1660. }
  1661. #endif
  1662. vec = (jsval *) JS_malloc(cx, 2 * (size_t) len * sizeof(jsval));
  1663. if (!vec)
  1664. return JS_FALSE;
  1665. /*
  1666. * Initialize vec as a root. We will clear elements of vec one by
  1667. * one while increasing tvr.count when we know that the property at
  1668. * the corresponding index exists and its value must be rooted.
  1669. *
  1670. * In this way when sorting a huge mostly sparse array we will not
  1671. * access the tail of vec corresponding to properties that do not
  1672. * exist, allowing OS to avoiding committing RAM. See bug 330812.
  1673. *
  1674. * After this point control must flow through label out: to exit.
  1675. */
  1676. JS_PUSH_TEMP_ROOT(cx, 0, vec, &tvr);
  1677. /*
  1678. * By ECMA 262, 15.4.4.11, a property that does not exist (which we
  1679. * call a "hole") is always greater than an existing property with
  1680. * value undefined and that is always greater than any other property.
  1681. * Thus to sort holes and undefs we simply count them, sort the rest
  1682. * of elements, append undefs after them and then make holes after
  1683. * undefs.
  1684. */
  1685. undefs = 0;
  1686. newlen = 0;
  1687. all_strings = JS_TRUE;
  1688. for (i = 0; i < len; i++) {
  1689. ok = JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP);
  1690. if (!ok)
  1691. goto out;
  1692. /* Clear vec[newlen] before including it in the rooted set. */
  1693. vec[newlen] = JSVAL_NULL;
  1694. tvr.count = newlen + 1;
  1695. ok = GetArrayElement(cx, obj, i, &hole, &vec[newlen]);
  1696. if (!ok)
  1697. goto out;
  1698. if (hole)
  1699. continue;
  1700. if (JSVAL_IS_VOID(vec[newlen])) {
  1701. ++undefs;
  1702. continue;
  1703. }
  1704. /* We know JSVAL_IS_STRING yields 0 or 1, so avoid a branch via &=. */
  1705. all_strings &= JSVAL_IS_STRING(vec[newlen]);
  1706. ++newlen;
  1707. }
  1708. if (newlen == 0) {
  1709. /* The array has only holes and undefs. */
  1710. ok = JS_TRUE;
  1711. goto out;
  1712. }
  1713. /*
  1714. * The first newlen elements of vec are copied from the array object
  1715. * (above). The remaining newlen positions are used as GC-rooted scratch
  1716. * space for mergesort. We must clear the space before including it to
  1717. * the root set covered by tvr.count. We assume JSVAL_NULL==0 to optimize
  1718. * initialization using memset.
  1719. */
  1720. mergesort_tmp = vec + newlen;
  1721. memset(mergesort_tmp, 0, newlen * sizeof(jsval));
  1722. tvr.count = newlen * 2;
  1723. /* Here len == 2 * (newlen + undefs + number_of_holes). */
  1724. if (fval == JSVAL_NULL) {
  1725. /*
  1726. * Sort using the default comparator converting all elements to
  1727. * strings.
  1728. */
  1729. if (all_strings) {
  1730. elemsize = sizeof(jsval);
  1731. } else {
  1732. /*
  1733. * To avoid string conversion on each compare we do it only once
  1734. * prior to sorting. But we also need the space for the original
  1735. * values to recover the sorting result. To reuse
  1736. * sort_compare_strings we move the original values to the odd
  1737. * indexes in vec, put the string conversion results in the even
  1738. * indexes and pass 2 * sizeof(jsval) as an element size to the
  1739. * sorting function. In this way sort_compare_strings will only
  1740. * see the string values when it casts the compare arguments as
  1741. * pointers to jsval.
  1742. *
  1743. * This requires doubling the temporary storage including the
  1744. * scratch space for the merge sort. Since vec already contains
  1745. * the rooted scratch space for newlen elements at the tail, we
  1746. * can use it to rearrange and convert to strings first and try
  1747. * realloc only when we know that we successfully converted all
  1748. * the elements.
  1749. */
  1750. #if JS_BITS_PER_WORD == 32
  1751. if ((size_t)newlen > ~(size_t)0 / (4 * sizeof(jsval))) {
  1752. js_ReportAllocationOverflow(cx);
  1753. ok = JS_FALSE;
  1754. goto out;
  1755. }
  1756. #endif
  1757. /*
  1758. * Rearrange and string-convert the elements of the vector from
  1759. * the tail here and, after sorting, move the results back
  1760. * starting from the start to prevent overwrite the existing
  1761. * elements.
  1762. */
  1763. i = newlen;
  1764. do {
  1765. --i;
  1766. ok = JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP);
  1767. if (!ok)
  1768. goto out;
  1769. v = vec[i];
  1770. str = js_ValueToString(cx, v);
  1771. if (!str) {
  1772. ok = JS_FALSE;
  1773. goto out;
  1774. }
  1775. vec[2 * i] = STRING_TO_JSVAL(str);
  1776. vec[2 * i + 1] = v;
  1777. } while (i != 0);
  1778. JS_ASSERT(tvr.u.array == vec);
  1779. vec = (jsval *) JS_realloc(cx, vec,
  1780. 4 * (size_t) newlen * sizeof(jsval));
  1781. if (!vec) {
  1782. vec = tvr.u.array;
  1783. ok = JS_FALSE;
  1784. goto out;
  1785. }
  1786. tvr.u.array = vec;
  1787. mergesort_tmp = vec + 2 * newlen;
  1788. memset(mergesort_tmp, 0, newlen * 2 * sizeof(jsval));
  1789. tvr.count = newlen * 4;
  1790. elemsize = 2 * sizeof(jsval);
  1791. }
  1792. ok = js_MergeSort(vec, (size_t) newlen, elemsize,
  1793. sort_compare_strings, cx, mergesort_tmp);
  1794. if (!ok)
  1795. goto out;
  1796. if (!all_strings) {
  1797. /*
  1798. * We want to make the following loop fast and to unroot the
  1799. * cached results of toString invocations before the operation
  1800. * callback has a chance to run the GC. For this reason we do
  1801. * not call JS_CHECK_OPERATION_LIMIT in the loop.
  1802. */
  1803. i = 0;
  1804. do {
  1805. vec[i] = vec[2 * i + 1];
  1806. } while (++i != newlen);
  1807. }
  1808. } else {
  1809. void *mark;
  1810. ca.context = cx;
  1811. ca.fval = fval;
  1812. ca.elemroot = js_AllocStack(cx, 2 + 2, &mark);
  1813. if (!ca.elemroot) {
  1814. ok = JS_FALSE;
  1815. goto out;
  1816. }
  1817. ok = js_MergeSort(vec, (size_t) newlen, sizeof(jsval),
  1818. sort_compare, &ca, mergesort_tmp);
  1819. js_FreeStack(cx, mark);
  1820. if (!ok)
  1821. goto out;
  1822. }
  1823. /*
  1824. * We no longer need to root the scratch space for the merge sort, so
  1825. * unroot it now to make the job of a potential GC under InitArrayElements
  1826. * easier.
  1827. */
  1828. tvr.count = newlen;
  1829. ok = InitArrayElements(cx, obj, 0, newlen, vec);
  1830. if (!ok)
  1831. goto out;
  1832. out:
  1833. JS_POP_TEMP_ROOT(cx, &tvr);
  1834. JS_free(cx, vec);
  1835. if (!ok)
  1836. return JS_FALSE;
  1837. /* Set undefs that sorted after the rest of elements. */
  1838. while (undefs != 0) {
  1839. --undefs;
  1840. if (!JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) ||
  1841. !SetArrayElement(cx, obj, newlen++, JSVAL_VOID)) {
  1842. return JS_FALSE;
  1843. }
  1844. }
  1845. /* Re-create any holes that sorted to the end of the array. */
  1846. while (len > newlen) {
  1847. if (!JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) ||
  1848. !DeleteArrayElement(cx, obj, --len)) {
  1849. return JS_FALSE;
  1850. }
  1851. }
  1852. *vp = OBJECT_TO_JSVAL(obj);
  1853. return JS_TRUE;
  1854. }
  1855. /*
  1856. * Perl-inspired push, pop, shift, unshift, and splice methods.
  1857. */
  1858. static JSBool
  1859. array_push_slowly(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
  1860. {
  1861. jsuint length, newlength;
  1862. if (!js_GetLengthProperty(cx, obj, &length))
  1863. return JS_FALSE;
  1864. newlength = length + argc;
  1865. if (!InitArrayElements(cx, obj, length, newlength, argv))
  1866. return JS_FALSE;
  1867. /* Per ECMA-262, return the new array length. */
  1868. if (!IndexToValue(cx, newlength, rval))
  1869. return JS_FALSE;
  1870. return js_SetLengthProperty(cx, obj, newlength);
  1871. }
  1872. static JSBool
  1873. array_push1_dense(JSContext* cx, JSObject* obj, jsval v, jsval *rval)
  1874. {
  1875. uint32 length = obj->fslots[JSSLOT_ARRAY_LENGTH];
  1876. if (INDEX_TOO_SPARSE(obj, length)) {
  1877. if (!js_MakeArraySlow(cx, obj))
  1878. return JS_FALSE;
  1879. return array_push_slowly(cx, obj, 1, &v, rval);
  1880. }
  1881. if (!EnsureLength(cx, obj, length + 1))
  1882. return JS_FALSE;
  1883. obj->fslots[JSSLOT_ARRAY_LENGTH] = length + 1;
  1884. JS_ASSERT(obj->dslots[length] == JSVAL_HOLE);
  1885. obj->fslots[JSSLOT_ARRAY_COUNT]++;
  1886. obj->dslots[length] = v;
  1887. return IndexToValue(cx, obj->fslots[JSSLOT_ARRAY_LENGTH], rval);
  1888. }
  1889. #ifdef JS_TRACER
  1890. static jsval FASTCALL
  1891. Array_p_push1(JSContext* cx, JSObject* obj, jsval v)
  1892. {
  1893. if (OBJ_IS_DENSE_ARRAY(cx, obj)
  1894. ? array_push1_dense(cx, obj, v, &v)
  1895. : array_push_slowly(cx, obj, 1, &v, &v)) {
  1896. return v;
  1897. }
  1898. return JSVAL_ERROR_COOKIE;
  1899. }
  1900. #endif
  1901. static JSBool
  1902. array_push(JSContext *cx, uintN argc, jsval *vp)
  1903. {
  1904. JSObject *obj;
  1905. /* Insist on one argument and obj of the expected class. */
  1906. obj = JS_THIS_OBJECT(cx, vp);
  1907. if (!obj)
  1908. re