Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2
* vim: set ts=8 sts=2 et sw=2 tw=80:
3
* This Source Code Form is subject to the terms of the Mozilla Public
4
* License, v. 2.0. If a copy of the MPL was not distributed with this
5
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "builtin/Array-inl.h"
8
9
#include "mozilla/ArrayUtils.h"
10
#include "mozilla/CheckedInt.h"
11
#include "mozilla/DebugOnly.h"
12
#include "mozilla/MathAlgorithms.h"
13
#include "mozilla/Maybe.h"
14
#include "mozilla/TextUtils.h"
15
16
#include <algorithm>
17
18
#include "jsapi.h"
19
#include "jsfriendapi.h"
20
#include "jsnum.h"
21
#include "jstypes.h"
22
23
#include "ds/Sort.h"
24
#include "gc/Heap.h"
25
#include "jit/InlinableNatives.h"
26
#include "js/Class.h"
27
#include "js/Conversions.h"
28
#include "js/PropertySpec.h"
29
#include "util/Poison.h"
30
#include "util/StringBuffer.h"
31
#include "util/Text.h"
32
#include "vm/ArgumentsObject.h"
33
#include "vm/Interpreter.h"
34
#include "vm/Iteration.h"
35
#include "vm/JSAtom.h"
36
#include "vm/JSContext.h"
37
#include "vm/JSFunction.h"
38
#include "vm/JSObject.h"
39
#include "vm/SelfHosting.h"
40
#include "vm/Shape.h"
41
#include "vm/TypedArrayObject.h"
42
#include "vm/WrapperObject.h"
43
44
#include "vm/ArgumentsObject-inl.h"
45
#include "vm/ArrayObject-inl.h"
46
#include "vm/Caches-inl.h"
47
#include "vm/GeckoProfiler-inl.h"
48
#include "vm/Interpreter-inl.h"
49
#include "vm/JSAtom-inl.h"
50
#include "vm/NativeObject-inl.h"
51
52
using namespace js;
53
54
using mozilla::Abs;
55
using mozilla::ArrayLength;
56
using mozilla::CeilingLog2;
57
using mozilla::CheckedInt;
58
using mozilla::DebugOnly;
59
using mozilla::IsAsciiDigit;
60
using mozilla::Maybe;
61
62
using JS::AutoCheckCannotGC;
63
using JS::IsArrayAnswer;
64
using JS::ToUint32;
65
66
bool JS::IsArray(JSContext* cx, HandleObject obj, IsArrayAnswer* answer) {
67
if (obj->is<ArrayObject>()) {
68
*answer = IsArrayAnswer::Array;
69
return true;
70
}
71
72
if (obj->is<ProxyObject>()) {
73
return Proxy::isArray(cx, obj, answer);
74
}
75
76
*answer = IsArrayAnswer::NotArray;
77
return true;
78
}
79
80
bool JS::IsArray(JSContext* cx, HandleObject obj, bool* isArray) {
81
IsArrayAnswer answer;
82
if (!IsArray(cx, obj, &answer)) {
83
return false;
84
}
85
86
if (answer == IsArrayAnswer::RevokedProxy) {
87
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
88
JSMSG_PROXY_REVOKED);
89
return false;
90
}
91
92
*isArray = answer == IsArrayAnswer::Array;
93
return true;
94
}
95
96
bool js::IsArrayFromJit(JSContext* cx, HandleObject obj, bool* isArray) {
97
return JS::IsArray(cx, obj, isArray);
98
}
99
100
// ES2017 7.1.15 ToLength, but clamped to the [0,2^32-2] range.
101
static bool ToLengthClamped(JSContext* cx, HandleValue v, uint32_t* out) {
102
if (v.isInt32()) {
103
int32_t i = v.toInt32();
104
*out = i < 0 ? 0 : i;
105
return true;
106
}
107
double d;
108
if (v.isDouble()) {
109
d = v.toDouble();
110
} else {
111
if (!ToNumber(cx, v, &d)) {
112
return false;
113
}
114
}
115
d = JS::ToInteger(d);
116
if (d <= 0.0) {
117
*out = 0;
118
} else if (d < double(UINT32_MAX - 1)) {
119
*out = uint32_t(d);
120
} else {
121
*out = UINT32_MAX;
122
}
123
return true;
124
}
125
126
bool js::GetLengthProperty(JSContext* cx, HandleObject obj, uint32_t* lengthp) {
127
if (obj->is<ArrayObject>()) {
128
*lengthp = obj->as<ArrayObject>().length();
129
return true;
130
}
131
132
if (obj->is<ArgumentsObject>()) {
133
ArgumentsObject& argsobj = obj->as<ArgumentsObject>();
134
if (!argsobj.hasOverriddenLength()) {
135
*lengthp = argsobj.initialLength();
136
return true;
137
}
138
}
139
140
RootedValue value(cx);
141
if (!GetProperty(cx, obj, obj, cx->names().length, &value)) {
142
return false;
143
}
144
145
if (!ToLengthClamped(cx, value, lengthp)) {
146
return false;
147
}
148
149
return true;
150
}
151
152
// ES2017 7.1.15 ToLength.
153
static bool ToLength(JSContext* cx, HandleValue v, uint64_t* out) {
154
if (v.isInt32()) {
155
int32_t i = v.toInt32();
156
*out = i < 0 ? 0 : i;
157
return true;
158
}
159
160
double d;
161
if (v.isDouble()) {
162
d = v.toDouble();
163
} else {
164
if (!ToNumber(cx, v, &d)) {
165
return false;
166
}
167
}
168
169
d = JS::ToInteger(d);
170
if (d <= 0.0) {
171
*out = 0;
172
} else {
173
*out = uint64_t(std::min(d, DOUBLE_INTEGRAL_PRECISION_LIMIT - 1));
174
}
175
return true;
176
}
177
178
static MOZ_ALWAYS_INLINE bool GetLengthProperty(JSContext* cx, HandleObject obj,
179
uint64_t* lengthp) {
180
if (obj->is<ArrayObject>()) {
181
*lengthp = obj->as<ArrayObject>().length();
182
return true;
183
}
184
185
if (obj->is<ArgumentsObject>()) {
186
ArgumentsObject& argsobj = obj->as<ArgumentsObject>();
187
if (!argsobj.hasOverriddenLength()) {
188
*lengthp = argsobj.initialLength();
189
return true;
190
}
191
}
192
193
RootedValue value(cx);
194
if (!GetProperty(cx, obj, obj, cx->names().length, &value)) {
195
return false;
196
}
197
198
return ToLength(cx, value, lengthp);
199
}
200
201
/*
202
* Determine if the id represents an array index.
203
*
204
* An id is an array index according to ECMA by (15.4):
205
*
206
* "Array objects give special treatment to a certain class of property names.
207
* A property name P (in the form of a string value) is an array index if and
208
* only if ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal
209
* to 2^32-1."
210
*
211
* This means the largest allowed index is actually 2^32-2 (4294967294).
212
*
213
* In our implementation, it would be sufficient to check for id.isInt32()
214
* except that by using signed 31-bit integers we miss the top half of the
215
* valid range. This function checks the string representation itself; note
216
* that calling a standard conversion routine might allow strings such as
217
* "08" or "4.0" as array indices, which they are not.
218
*
219
*/
220
template <typename CharT>
221
static bool StringIsArrayIndexHelper(const CharT* s, uint32_t length,
222
uint32_t* indexp) {
223
const CharT* end = s + length;
224
225
if (length == 0 || length > (sizeof("4294967294") - 1) || !IsAsciiDigit(*s)) {
226
return false;
227
}
228
229
uint32_t c = 0, previous = 0;
230
uint32_t index = AsciiDigitToNumber(*s++);
231
232
/* Don't allow leading zeros. */
233
if (index == 0 && s != end) {
234
return false;
235
}
236
237
for (; s < end; s++) {
238
if (!IsAsciiDigit(*s)) {
239
return false;
240
}
241
242
previous = index;
243
c = AsciiDigitToNumber(*s);
244
index = 10 * index + c;
245
}
246
247
/* Make sure we didn't overflow. */
248
if (previous < (MAX_ARRAY_INDEX / 10) ||
249
(previous == (MAX_ARRAY_INDEX / 10) && c <= (MAX_ARRAY_INDEX % 10))) {
250
MOZ_ASSERT(index <= MAX_ARRAY_INDEX);
251
*indexp = index;
252
return true;
253
}
254
255
return false;
256
}
257
258
JS_FRIEND_API bool js::StringIsArrayIndex(JSLinearString* str,
259
uint32_t* indexp) {
260
AutoCheckCannotGC nogc;
261
return str->hasLatin1Chars()
262
? StringIsArrayIndexHelper(str->latin1Chars(nogc), str->length(),
263
indexp)
264
: StringIsArrayIndexHelper(str->twoByteChars(nogc), str->length(),
265
indexp);
266
}
267
268
JS_FRIEND_API bool js::StringIsArrayIndex(const char16_t* str, uint32_t length,
269
uint32_t* indexp) {
270
return StringIsArrayIndexHelper(str, length, indexp);
271
}
272
273
JS_FRIEND_API bool js::StringIsArrayIndex(const char* str, uint32_t length,
274
uint32_t* indexp) {
275
return StringIsArrayIndexHelper(str, length, indexp);
276
}
277
278
template <typename T>
279
static bool ToId(JSContext* cx, T index, MutableHandleId id);
280
281
template <>
282
bool ToId(JSContext* cx, uint32_t index, MutableHandleId id) {
283
return IndexToId(cx, index, id);
284
}
285
286
template <>
287
bool ToId(JSContext* cx, uint64_t index, MutableHandleId id) {
288
MOZ_ASSERT(index < uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT));
289
290
if (index == uint32_t(index)) {
291
return IndexToId(cx, uint32_t(index), id);
292
}
293
294
Value tmp = DoubleValue(index);
295
return ValueToId<CanGC>(cx, HandleValue::fromMarkedLocation(&tmp), id);
296
}
297
298
/*
299
* If the property at the given index exists, get its value into |vp| and set
300
* |*hole| to false. Otherwise set |*hole| to true and |vp| to Undefined.
301
*/
302
template <typename T>
303
static bool HasAndGetElement(JSContext* cx, HandleObject obj,
304
HandleObject receiver, T index, bool* hole,
305
MutableHandleValue vp) {
306
if (obj->isNative()) {
307
NativeObject* nobj = &obj->as<NativeObject>();
308
if (index < nobj->getDenseInitializedLength()) {
309
vp.set(nobj->getDenseElement(size_t(index)));
310
if (!vp.isMagic(JS_ELEMENTS_HOLE)) {
311
*hole = false;
312
return true;
313
}
314
}
315
if (nobj->is<ArgumentsObject>() && index <= UINT32_MAX) {
316
if (nobj->as<ArgumentsObject>().maybeGetElement(uint32_t(index), vp)) {
317
*hole = false;
318
return true;
319
}
320
}
321
}
322
323
RootedId id(cx);
324
if (!ToId(cx, index, &id)) {
325
return false;
326
}
327
328
bool found;
329
if (!HasProperty(cx, obj, id, &found)) {
330
return false;
331
}
332
333
if (found) {
334
if (!GetProperty(cx, obj, receiver, id, vp)) {
335
return false;
336
}
337
} else {
338
vp.setUndefined();
339
}
340
*hole = !found;
341
return true;
342
}
343
344
template <typename T>
345
static inline bool HasAndGetElement(JSContext* cx, HandleObject obj, T index,
346
bool* hole, MutableHandleValue vp) {
347
return HasAndGetElement(cx, obj, obj, index, hole, vp);
348
}
349
350
bool ElementAdder::append(JSContext* cx, HandleValue v) {
351
MOZ_ASSERT(index_ < length_);
352
if (resObj_) {
353
NativeObject* resObj = &resObj_->as<NativeObject>();
354
DenseElementResult result =
355
resObj->setOrExtendDenseElements(cx, index_, v.address(), 1);
356
if (result == DenseElementResult::Failure) {
357
return false;
358
}
359
if (result == DenseElementResult::Incomplete) {
360
if (!DefineDataElement(cx, resObj_, index_, v)) {
361
return false;
362
}
363
}
364
} else {
365
vp_[index_] = v;
366
}
367
index_++;
368
return true;
369
}
370
371
void ElementAdder::appendHole() {
372
MOZ_ASSERT(getBehavior_ == ElementAdder::CheckHasElemPreserveHoles);
373
MOZ_ASSERT(index_ < length_);
374
if (!resObj_) {
375
vp_[index_].setMagic(JS_ELEMENTS_HOLE);
376
}
377
index_++;
378
}
379
380
bool js::GetElementsWithAdder(JSContext* cx, HandleObject obj,
381
HandleObject receiver, uint32_t begin,
382
uint32_t end, ElementAdder* adder) {
383
MOZ_ASSERT(begin <= end);
384
385
RootedValue val(cx);
386
for (uint32_t i = begin; i < end; i++) {
387
if (adder->getBehavior() == ElementAdder::CheckHasElemPreserveHoles) {
388
bool hole;
389
if (!HasAndGetElement(cx, obj, receiver, i, &hole, &val)) {
390
return false;
391
}
392
if (hole) {
393
adder->appendHole();
394
continue;
395
}
396
} else {
397
MOZ_ASSERT(adder->getBehavior() == ElementAdder::GetElement);
398
if (!GetElement(cx, obj, receiver, i, &val)) {
399
return false;
400
}
401
}
402
if (!adder->append(cx, val)) {
403
return false;
404
}
405
}
406
407
return true;
408
}
409
410
static inline bool IsPackedArrayOrNoExtraIndexedProperties(JSObject* obj,
411
uint64_t length) {
412
return (IsPackedArray(obj) && obj->as<ArrayObject>().length() == length) ||
413
!ObjectMayHaveExtraIndexedProperties(obj);
414
}
415
416
static bool GetDenseElements(NativeObject* aobj, uint32_t length, Value* vp) {
417
MOZ_ASSERT(IsPackedArrayOrNoExtraIndexedProperties(aobj, length));
418
419
if (length > aobj->getDenseInitializedLength()) {
420
return false;
421
}
422
423
for (size_t i = 0; i < length; i++) {
424
vp[i] = aobj->getDenseElement(i);
425
426
// No other indexed properties so hole => undefined.
427
if (vp[i].isMagic(JS_ELEMENTS_HOLE)) {
428
vp[i] = UndefinedValue();
429
}
430
}
431
432
return true;
433
}
434
435
bool js::GetElements(JSContext* cx, HandleObject aobj, uint32_t length,
436
Value* vp) {
437
if (IsPackedArrayOrNoExtraIndexedProperties(aobj, length)) {
438
if (GetDenseElements(&aobj->as<NativeObject>(), length, vp)) {
439
return true;
440
}
441
}
442
443
if (aobj->is<ArgumentsObject>()) {
444
ArgumentsObject& argsobj = aobj->as<ArgumentsObject>();
445
if (!argsobj.hasOverriddenLength()) {
446
if (argsobj.maybeGetElements(0, length, vp)) {
447
return true;
448
}
449
}
450
}
451
452
if (aobj->is<TypedArrayObject>()) {
453
Handle<TypedArrayObject*> typedArray = aobj.as<TypedArrayObject>();
454
if (typedArray->length() == length) {
455
return TypedArrayObject::getElements(cx, typedArray, vp);
456
}
457
}
458
459
if (js::GetElementsOp op = aobj->getOpsGetElements()) {
460
ElementAdder adder(cx, vp, length, ElementAdder::GetElement);
461
return op(cx, aobj, 0, length, &adder);
462
}
463
464
for (uint32_t i = 0; i < length; i++) {
465
if (!GetElement(cx, aobj, aobj, i,
466
MutableHandleValue::fromMarkedLocation(&vp[i]))) {
467
return false;
468
}
469
}
470
471
return true;
472
}
473
474
static inline bool GetArrayElement(JSContext* cx, HandleObject obj,
475
uint64_t index, MutableHandleValue vp) {
476
if (obj->isNative()) {
477
NativeObject* nobj = &obj->as<NativeObject>();
478
if (index < nobj->getDenseInitializedLength()) {
479
vp.set(nobj->getDenseElement(size_t(index)));
480
if (!vp.isMagic(JS_ELEMENTS_HOLE)) {
481
return true;
482
}
483
}
484
485
if (nobj->is<ArgumentsObject>() && index <= UINT32_MAX) {
486
if (nobj->as<ArgumentsObject>().maybeGetElement(uint32_t(index), vp)) {
487
return true;
488
}
489
}
490
}
491
492
RootedId id(cx);
493
if (!ToId(cx, index, &id)) {
494
return false;
495
}
496
return GetProperty(cx, obj, obj, id, vp);
497
}
498
499
static inline bool DefineArrayElement(JSContext* cx, HandleObject obj,
500
uint64_t index, HandleValue value) {
501
RootedId id(cx);
502
if (!ToId(cx, index, &id)) {
503
return false;
504
}
505
return DefineDataProperty(cx, obj, id, value);
506
}
507
508
// Set the value of the property at the given index to v.
509
static inline bool SetArrayElement(JSContext* cx, HandleObject obj,
510
uint64_t index, HandleValue v) {
511
RootedId id(cx);
512
if (!ToId(cx, index, &id)) {
513
return false;
514
}
515
516
return SetProperty(cx, obj, id, v);
517
}
518
519
/*
520
* Attempt to delete the element |index| from |obj| as if by
521
* |obj.[[Delete]](index)|.
522
*
523
* If an error occurs while attempting to delete the element (that is, the call
524
* to [[Delete]] threw), return false.
525
*
526
* Otherwise call result.succeed() or result.fail() to indicate whether the
527
* deletion attempt succeeded (that is, whether the call to [[Delete]] returned
528
* true or false). (Deletes generally fail only when the property is
529
* non-configurable, but proxies may implement different semantics.)
530
*/
531
static bool DeleteArrayElement(JSContext* cx, HandleObject obj, uint64_t index,
532
ObjectOpResult& result) {
533
if (obj->is<ArrayObject>() && !obj->as<NativeObject>().isIndexed() &&
534
!obj->as<NativeObject>().denseElementsAreSealed()) {
535
ArrayObject* aobj = &obj->as<ArrayObject>();
536
if (index <= UINT32_MAX) {
537
uint32_t idx = uint32_t(index);
538
if (idx < aobj->getDenseInitializedLength()) {
539
if (!aobj->maybeCopyElementsForWrite(cx)) {
540
return false;
541
}
542
if (idx + 1 == aobj->getDenseInitializedLength()) {
543
aobj->setDenseInitializedLengthMaybeNonExtensible(cx, idx);
544
} else {
545
aobj->markDenseElementsNotPacked(cx);
546
aobj->setDenseElement(idx, MagicValue(JS_ELEMENTS_HOLE));
547
}
548
if (!SuppressDeletedElement(cx, obj, idx)) {
549
return false;
550
}
551
}
552
}
553
554
return result.succeed();
555
}
556
557
RootedId id(cx);
558
if (!ToId(cx, index, &id)) {
559
return false;
560
}
561
return DeleteProperty(cx, obj, id, result);
562
}
563
564
/* ES6 draft rev 32 (2 Febr 2015) 7.3.7 */
565
static bool DeletePropertyOrThrow(JSContext* cx, HandleObject obj,
566
uint64_t index) {
567
ObjectOpResult success;
568
if (!DeleteArrayElement(cx, obj, index, success)) {
569
return false;
570
}
571
if (!success) {
572
RootedId id(cx);
573
if (!ToId(cx, index, &id)) {
574
return false;
575
}
576
return success.reportError(cx, obj, id);
577
}
578
return true;
579
}
580
581
static bool DeletePropertiesOrThrow(JSContext* cx, HandleObject obj,
582
uint64_t len, uint64_t finalLength) {
583
if (obj->is<ArrayObject>() && !obj->as<NativeObject>().isIndexed() &&
584
!obj->as<NativeObject>().denseElementsAreSealed()) {
585
if (len <= UINT32_MAX) {
586
// Skip forward to the initialized elements of this array.
587
len = std::min(uint32_t(len),
588
obj->as<ArrayObject>().getDenseInitializedLength());
589
}
590
}
591
592
for (uint64_t k = len; k > finalLength; k--) {
593
if (!CheckForInterrupt(cx)) {
594
return false;
595
}
596
597
if (!DeletePropertyOrThrow(cx, obj, k - 1)) {
598
return false;
599
}
600
}
601
return true;
602
}
603
604
static bool SetArrayLengthProperty(JSContext* cx, HandleArrayObject obj,
605
HandleValue value) {
606
RootedId id(cx, NameToId(cx->names().length));
607
ObjectOpResult result;
608
if (obj->lengthIsWritable()) {
609
if (!ArraySetLength(cx, obj, id, JSPROP_PERMANENT, value, result)) {
610
return false;
611
}
612
} else {
613
MOZ_ALWAYS_TRUE(result.fail(JSMSG_READ_ONLY));
614
}
615
return result.checkStrict(cx, obj, id);
616
}
617
618
static bool SetLengthProperty(JSContext* cx, HandleObject obj,
619
uint64_t length) {
620
MOZ_ASSERT(length < uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT));
621
622
RootedValue v(cx, NumberValue(length));
623
if (obj->is<ArrayObject>()) {
624
return SetArrayLengthProperty(cx, obj.as<ArrayObject>(), v);
625
}
626
return SetProperty(cx, obj, cx->names().length, v);
627
}
628
629
bool js::SetLengthProperty(JSContext* cx, HandleObject obj, uint32_t length) {
630
RootedValue v(cx, NumberValue(length));
631
if (obj->is<ArrayObject>()) {
632
return SetArrayLengthProperty(cx, obj.as<ArrayObject>(), v);
633
}
634
return SetProperty(cx, obj, cx->names().length, v);
635
}
636
637
static bool array_length_getter(JSContext* cx, HandleObject obj, HandleId id,
638
MutableHandleValue vp) {
639
vp.setNumber(obj->as<ArrayObject>().length());
640
return true;
641
}
642
643
static bool array_length_setter(JSContext* cx, HandleObject obj, HandleId id,
644
HandleValue v, ObjectOpResult& result) {
645
MOZ_ASSERT(id == NameToId(cx->names().length));
646
647
if (!obj->is<ArrayObject>()) {
648
// This array .length property was found on the prototype
649
// chain. Ideally the setter should not have been called, but since
650
// we're here, do an impression of SetPropertyByDefining.
651
return DefineDataProperty(cx, obj, id, v, JSPROP_ENUMERATE, result);
652
}
653
654
HandleArrayObject arr = obj.as<ArrayObject>();
655
MOZ_ASSERT(arr->lengthIsWritable(),
656
"setter shouldn't be called if property is non-writable");
657
658
return ArraySetLength(cx, arr, id, JSPROP_PERMANENT, v, result);
659
}
660
661
struct ReverseIndexComparator {
662
bool operator()(const uint32_t& a, const uint32_t& b, bool* lessOrEqualp) {
663
MOZ_ASSERT(a != b, "how'd we get duplicate indexes?");
664
*lessOrEqualp = b <= a;
665
return true;
666
}
667
};
668
669
static bool MaybeInIteration(HandleObject obj, JSContext* cx) {
670
/*
671
* Don't optimize if the array might be in the midst of iteration. We
672
* rely on this to be able to safely move dense array elements around with
673
* just a memmove (see NativeObject::moveDenseArrayElements), without worrying
674
* about updating any in-progress enumerators for properties implicitly
675
* deleted if a hole is moved from one location to another location not yet
676
* visited. See bug 690622.
677
*
678
* Note that it's fine to optimize if |obj| is on the prototype of another
679
* object: SuppressDeletedProperty only suppresses properties deleted from
680
* the iterated object itself.
681
*/
682
683
if (MOZ_LIKELY(!ObjectRealm::get(obj).objectMaybeInIteration(obj))) {
684
return false;
685
}
686
687
ObjectGroup* group = JSObject::getGroup(cx, obj);
688
if (MOZ_UNLIKELY(!group)) {
689
cx->recoverFromOutOfMemory();
690
return true;
691
}
692
693
AutoSweepObjectGroup sweep(group);
694
if (MOZ_UNLIKELY(group->hasAllFlags(sweep, OBJECT_FLAG_ITERATED))) {
695
return true;
696
}
697
698
return false;
699
}
700
701
/* ES6 draft rev 34 (2015 Feb 20) 9.4.2.4 ArraySetLength */
702
bool js::ArraySetLength(JSContext* cx, Handle<ArrayObject*> arr, HandleId id,
703
unsigned attrs, HandleValue value,
704
ObjectOpResult& result) {
705
MOZ_ASSERT(id == NameToId(cx->names().length));
706
707
if (!arr->maybeCopyElementsForWrite(cx)) {
708
return false;
709
}
710
711
// Step 1.
712
uint32_t newLen;
713
if (attrs & JSPROP_IGNORE_VALUE) {
714
MOZ_ASSERT(value.isUndefined());
715
716
// The spec has us calling OrdinaryDefineOwnProperty if
717
// Desc.[[Value]] is absent, but our implementation is so different that
718
// this is impossible. Instead, set newLen to the current length and
719
// proceed to step 9.
720
newLen = arr->length();
721
} else {
722
// Step 2 is irrelevant in our implementation.
723
724
// Step 3.
725
if (!ToUint32(cx, value, &newLen)) {
726
return false;
727
}
728
729
// Step 4.
730
double d;
731
if (!ToNumber(cx, value, &d)) {
732
return false;
733
}
734
735
// Step 5.
736
if (d != newLen) {
737
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
738
JSMSG_BAD_ARRAY_LENGTH);
739
return false;
740
}
741
742
// Steps 6-8 are irrelevant in our implementation.
743
}
744
745
// Steps 9-11.
746
bool lengthIsWritable = arr->lengthIsWritable();
747
#ifdef DEBUG
748
{
749
RootedShape lengthShape(cx, arr->lookupPure(id));
750
MOZ_ASSERT(lengthShape);
751
MOZ_ASSERT(lengthShape->writable() == lengthIsWritable);
752
}
753
#endif
754
uint32_t oldLen = arr->length();
755
756
// Part of steps 1.a, 12.a, and 16: Fail if we're being asked to change
757
// enumerability or configurability, or otherwise break the object
758
// invariants. (ES6 checks these by calling OrdinaryDefineOwnProperty, but
759
// in SM, the array length property is hardly ordinary.)
760
if ((attrs & (JSPROP_PERMANENT | JSPROP_IGNORE_PERMANENT)) == 0 ||
761
(attrs & (JSPROP_ENUMERATE | JSPROP_IGNORE_ENUMERATE)) ==
762
JSPROP_ENUMERATE ||
763
(attrs & (JSPROP_GETTER | JSPROP_SETTER)) != 0 ||
764
(!lengthIsWritable &&
765
(attrs & (JSPROP_READONLY | JSPROP_IGNORE_READONLY)) == 0)) {
766
return result.fail(JSMSG_CANT_REDEFINE_PROP);
767
}
768
769
// Steps 12-13 for arrays with non-writable length.
770
if (!lengthIsWritable) {
771
if (newLen == oldLen) {
772
return result.succeed();
773
}
774
775
return result.fail(JSMSG_CANT_REDEFINE_ARRAY_LENGTH);
776
}
777
778
// Step 19.
779
bool succeeded = true;
780
do {
781
// The initialized length and capacity of an array only need updating
782
// when non-hole elements are added or removed, which doesn't happen
783
// when array length stays the same or increases.
784
if (newLen >= oldLen) {
785
break;
786
}
787
788
// Attempt to propagate dense-element optimization tricks, if possible,
789
// and avoid the generic (and accordingly slow) deletion code below.
790
// We can only do this if there are only densely-indexed elements.
791
// Once there's a sparse indexed element, there's no good way to know,
792
// save by enumerating all the properties to find it. But we *have* to
793
// know in case that sparse indexed element is non-configurable, as
794
// that element must prevent any deletions below it. Bug 586842 should
795
// fix this inefficiency by moving indexed storage to be entirely
796
// separate from non-indexed storage.
797
// A second reason for this optimization to be invalid is an active
798
// for..in iteration over the array. Keys deleted before being reached
799
// during the iteration must not be visited, and suppressing them here
800
// would be too costly.
801
// This optimization is also invalid when there are sealed
802
// (non-configurable) elements.
803
if (!arr->isIndexed() && !MaybeInIteration(arr, cx) &&
804
!arr->denseElementsAreSealed()) {
805
if (!arr->maybeCopyElementsForWrite(cx)) {
806
return false;
807
}
808
809
uint32_t oldCapacity = arr->getDenseCapacity();
810
uint32_t oldInitializedLength = arr->getDenseInitializedLength();
811
MOZ_ASSERT(oldCapacity >= oldInitializedLength);
812
if (oldInitializedLength > newLen) {
813
arr->setDenseInitializedLengthMaybeNonExtensible(cx, newLen);
814
}
815
if (oldCapacity > newLen) {
816
if (arr->isExtensible()) {
817
arr->shrinkElements(cx, newLen);
818
} else {
819
MOZ_ASSERT(arr->getDenseInitializedLength() ==
820
arr->getDenseCapacity());
821
}
822
}
823
824
// We've done the work of deleting any dense elements needing
825
// deletion, and there are no sparse elements. Thus we can skip
826
// straight to defining the length.
827
break;
828
}
829
830
// Step 15.
831
//
832
// Attempt to delete all elements above the new length, from greatest
833
// to least. If any of these deletions fails, we're supposed to define
834
// the length to one greater than the index that couldn't be deleted,
835
// *with the property attributes specified*. This might convert the
836
// length to be not the value specified, yet non-writable. (You may be
837
// forgiven for thinking these are interesting semantics.) Example:
838
//
839
// var arr =
840
// Object.defineProperty([0, 1, 2, 3], 1, { writable: false });
841
// Object.defineProperty(arr, "length",
842
// { value: 0, writable: false });
843
//
844
// will convert |arr| to an array of non-writable length two, then
845
// throw a TypeError.
846
//
847
// We implement this behavior, in the relevant lops below, by setting
848
// |succeeded| to false. Then we exit the loop, define the length
849
// appropriately, and only then throw a TypeError, if necessary.
850
uint32_t gap = oldLen - newLen;
851
const uint32_t RemoveElementsFastLimit = 1 << 24;
852
if (gap < RemoveElementsFastLimit) {
853
// If we're removing a relatively small number of elements, just do
854
// it exactly by the spec.
855
while (newLen < oldLen) {
856
// Step 15a.
857
oldLen--;
858
859
// Steps 15b-d.
860
ObjectOpResult deleteSucceeded;
861
if (!DeleteElement(cx, arr, oldLen, deleteSucceeded)) {
862
return false;
863
}
864
if (!deleteSucceeded) {
865
newLen = oldLen + 1;
866
succeeded = false;
867
break;
868
}
869
}
870
} else {
871
// If we're removing a large number of elements from an array
872
// that's probably sparse, try a different tack. Get all the own
873
// property names, sift out the indexes in the deletion range into
874
// a vector, sort the vector greatest to least, then delete the
875
// indexes greatest to least using that vector. See bug 322135.
876
//
877
// This heuristic's kind of a huge guess -- "large number of
878
// elements" and "probably sparse" are completely unprincipled
879
// predictions. In the long run, bug 586842 will support the right
880
// fix: store sparse elements in a sorted data structure that
881
// permits fast in-reverse-order traversal and concurrent removals.
882
883
Vector<uint32_t> indexes(cx);
884
{
885
RootedIdVector props(cx);
886
if (!GetPropertyKeys(cx, arr, JSITER_OWNONLY | JSITER_HIDDEN, &props)) {
887
return false;
888
}
889
890
for (size_t i = 0; i < props.length(); i++) {
891
if (!CheckForInterrupt(cx)) {
892
return false;
893
}
894
895
uint32_t index;
896
if (!IdIsIndex(props[i], &index)) {
897
continue;
898
}
899
900
if (index >= newLen && index < oldLen) {
901
if (!indexes.append(index)) {
902
return false;
903
}
904
}
905
}
906
}
907
908
uint32_t count = indexes.length();
909
{
910
// We should use radix sort to be O(n), but this is uncommon
911
// enough that we'll punt til someone complains.
912
Vector<uint32_t> scratch(cx);
913
if (!scratch.resize(count)) {
914
return false;
915
}
916
MOZ_ALWAYS_TRUE(MergeSort(indexes.begin(), count, scratch.begin(),
917
ReverseIndexComparator()));
918
}
919
920
uint32_t index = UINT32_MAX;
921
for (uint32_t i = 0; i < count; i++) {
922
MOZ_ASSERT(indexes[i] < index, "indexes should never repeat");
923
index = indexes[i];
924
925
// Steps 15b-d.
926
ObjectOpResult deleteSucceeded;
927
if (!DeleteElement(cx, arr, index, deleteSucceeded)) {
928
return false;
929
}
930
if (!deleteSucceeded) {
931
newLen = index + 1;
932
succeeded = false;
933
break;
934
}
935
}
936
}
937
} while (false);
938
939
// Update array length. Technically we should have been doing this
940
// throughout the loop, in step 19.d.iii.
941
arr->setLength(cx, newLen);
942
943
// Step 20.
944
if (attrs & JSPROP_READONLY) {
945
// Yes, we totally drop a non-stub getter/setter from a defineProperty
946
// API call on the floor here. Given that getter/setter will go away in
947
// the long run, with accessors replacing them both internally and at the
948
// API level, just run with this.
949
RootedShape lengthShape(cx, arr->lookup(cx, id));
950
if (!NativeObject::changeProperty(
951
cx, arr, lengthShape, lengthShape->attributes() | JSPROP_READONLY,
952
array_length_getter, array_length_setter)) {
953
return false;
954
}
955
}
956
957
// All operations past here until the |!succeeded| code must be infallible,
958
// so that all element fields remain properly synchronized.
959
960
// Trim the initialized length, if needed, to preserve the <= length
961
// invariant. (Capacity was already reduced during element deletion, if
962
// necessary.)
963
ObjectElements* header = arr->getElementsHeader();
964
header->initializedLength = std::min(header->initializedLength, newLen);
965
966
if (!arr->isExtensible()) {
967
arr->shrinkCapacityToInitializedLength(cx);
968
}
969
970
if (attrs & JSPROP_READONLY) {
971
arr->setNonWritableLength(cx);
972
}
973
974
if (!succeeded) {
975
return result.fail(JSMSG_CANT_TRUNCATE_ARRAY);
976
}
977
978
return result.succeed();
979
}
980
981
static bool array_addProperty(JSContext* cx, HandleObject obj, HandleId id,
982
HandleValue v) {
983
ArrayObject* arr = &obj->as<ArrayObject>();
984
985
uint32_t index;
986
if (!IdIsIndex(id, &index)) {
987
return true;
988
}
989
990
uint32_t length = arr->length();
991
if (index >= length) {
992
MOZ_ASSERT(arr->lengthIsWritable(),
993
"how'd this element get added if length is non-writable?");
994
arr->setLength(cx, index + 1);
995
}
996
return true;
997
}
998
999
static inline bool ObjectMayHaveExtraIndexedOwnProperties(JSObject* obj) {
1000
if (!obj->isNative()) {
1001
return true;
1002
}
1003
1004
if (obj->as<NativeObject>().isIndexed()) {
1005
return true;
1006
}
1007
1008
if (obj->is<TypedArrayObject>()) {
1009
return true;
1010
}
1011
1012
return ClassMayResolveId(*obj->runtimeFromAnyThread()->commonNames,
1013
obj->getClass(), INT_TO_JSID(0), obj);
1014
}
1015
1016
/*
1017
* Whether obj may have indexed properties anywhere besides its dense
1018
* elements. This includes other indexed properties in its shape hierarchy, and
1019
* indexed properties or elements along its prototype chain.
1020
*/
1021
bool js::ObjectMayHaveExtraIndexedProperties(JSObject* obj) {
1022
MOZ_ASSERT_IF(obj->hasDynamicPrototype(), !obj->isNative());
1023
1024
if (ObjectMayHaveExtraIndexedOwnProperties(obj)) {
1025
return true;
1026
}
1027
1028
do {
1029
MOZ_ASSERT(obj->hasStaticPrototype(),
1030
"dynamic-prototype objects must be non-native, ergo must "
1031
"have failed ObjectMayHaveExtraIndexedOwnProperties");
1032
1033
obj = obj->staticPrototype();
1034
if (!obj) {
1035
return false; // no extra indexed properties found
1036
}
1037
1038
if (ObjectMayHaveExtraIndexedOwnProperties(obj)) {
1039
return true;
1040
}
1041
if (obj->as<NativeObject>().getDenseInitializedLength() != 0) {
1042
return true;
1043
}
1044
} while (true);
1045
}
1046
1047
static bool AddLengthProperty(JSContext* cx, HandleArrayObject obj) {
1048
// Add the 'length' property for a newly created array. Shapes are shared
1049
// across realms within a zone and because we update the initial shape with
1050
// a Shape that contains the length-property (in NewArray), it's possible
1051
// the length property has already been defined.
1052
1053
Shape* shape = obj->lastProperty();
1054
if (!shape->isEmptyShape()) {
1055
MOZ_ASSERT(JSID_IS_ATOM(shape->propidRaw(), cx->names().length));
1056
MOZ_ASSERT(shape->previous()->isEmptyShape());
1057
return true;
1058
}
1059
1060
RootedId lengthId(cx, NameToId(cx->names().length));
1061
return NativeObject::addAccessorProperty(
1062
cx, obj, lengthId, array_length_getter, array_length_setter,
1063
JSPROP_PERMANENT);
1064
}
1065
1066
static bool IsArrayConstructor(const JSObject* obj) {
1067
// Note: this also returns true for cross-realm Array constructors in the
1068
// same compartment.
1069
return IsNativeFunction(obj, ArrayConstructor);
1070
}
1071
1072
static bool IsArrayConstructor(const Value& v) {
1073
return v.isObject() && IsArrayConstructor(&v.toObject());
1074
}
1075
1076
bool js::IsCrossRealmArrayConstructor(JSContext* cx, const Value& v,
1077
bool* result) {
1078
if (!v.isObject()) {
1079
*result = false;
1080
return true;
1081
}
1082
1083
JSObject* obj = &v.toObject();
1084
if (obj->is<WrapperObject>()) {
1085
obj = CheckedUnwrapDynamic(obj, cx);
1086
if (!obj) {
1087
ReportAccessDenied(cx);
1088
return false;
1089
}
1090
}
1091
1092
*result =
1093
IsArrayConstructor(obj) && obj->as<JSFunction>().realm() != cx->realm();
1094
return true;
1095
}
1096
1097
static MOZ_ALWAYS_INLINE bool IsArraySpecies(JSContext* cx,
1098
HandleObject origArray) {
1099
if (MOZ_UNLIKELY(origArray->is<ProxyObject>())) {
1100
if (origArray->getClass()->isDOMClass()) {
1101
#ifdef DEBUG
1102
// We assume DOM proxies never return true for IsArray.
1103
IsArrayAnswer answer;
1104
MOZ_ASSERT(Proxy::isArray(cx, origArray, &answer));
1105
MOZ_ASSERT(answer == IsArrayAnswer::NotArray);
1106
#endif
1107
return true;
1108
}
1109
return false;
1110
}
1111
1112
// 9.4.2.3 Step 4. Non-array objects always use the default constructor.
1113
if (!origArray->is<ArrayObject>()) {
1114
return true;
1115
}
1116
1117
if (cx->realm()->arraySpeciesLookup.tryOptimizeArray(
1118
cx, &origArray->as<ArrayObject>())) {
1119
return true;
1120
}
1121
1122
Value ctor;
1123
if (!GetPropertyPure(cx, origArray, NameToId(cx->names().constructor),
1124
&ctor)) {
1125
return false;
1126
}
1127
1128
if (!IsArrayConstructor(ctor)) {
1129
return ctor.isUndefined();
1130
}
1131
1132
// 9.4.2.3 Step 6.c. Use the current realm's constructor if |ctor| is a
1133
// cross-realm Array constructor.
1134
if (cx->realm() != ctor.toObject().as<JSFunction>().realm()) {
1135
return true;
1136
}
1137
1138
jsid speciesId = SYMBOL_TO_JSID(cx->wellKnownSymbols().species);
1139
JSFunction* getter;
1140
if (!GetGetterPure(cx, &ctor.toObject(), speciesId, &getter)) {
1141
return false;
1142
}
1143
1144
if (!getter) {
1145
return false;
1146
}
1147
1148
return IsSelfHostedFunctionWithName(getter, cx->names().ArraySpecies);
1149
}
1150
1151
static bool ArraySpeciesCreate(JSContext* cx, HandleObject origArray,
1152
uint64_t length, MutableHandleObject arr) {
1153
MOZ_ASSERT(length < DOUBLE_INTEGRAL_PRECISION_LIMIT);
1154
1155
FixedInvokeArgs<2> args(cx);
1156
1157
args[0].setObject(*origArray);
1158
args[1].set(NumberValue(length));
1159
1160
RootedValue rval(cx);
1161
if (!CallSelfHostedFunction(cx, cx->names().ArraySpeciesCreate,
1162
UndefinedHandleValue, args, &rval)) {
1163
return false;
1164
}
1165
1166
MOZ_ASSERT(rval.isObject());
1167
arr.set(&rval.toObject());
1168
return true;
1169
}
1170
1171
static bool array_toSource(JSContext* cx, unsigned argc, Value* vp) {
1172
if (!CheckRecursionLimit(cx)) {
1173
return false;
1174
}
1175
1176
CallArgs args = CallArgsFromVp(argc, vp);
1177
1178
if (!args.thisv().isObject()) {
1179
ReportIncompatible(cx, args);
1180
return false;
1181
}
1182
1183
Rooted<JSObject*> obj(cx, &args.thisv().toObject());
1184
RootedValue elt(cx);
1185
1186
AutoCycleDetector detector(cx, obj);
1187
if (!detector.init()) {
1188
return false;
1189
}
1190
1191
JSStringBuilder sb(cx);
1192
1193
if (detector.foundCycle()) {
1194
if (!sb.append("[]")) {
1195
return false;
1196
}
1197
goto make_string;
1198
}
1199
1200
if (!sb.append('[')) {
1201
return false;
1202
}
1203
1204
uint64_t length;
1205
if (!GetLengthProperty(cx, obj, &length)) {
1206
return false;
1207
}
1208
1209
for (uint64_t index = 0; index < length; index++) {
1210
bool hole;
1211
if (!CheckForInterrupt(cx) ||
1212
!HasAndGetElement(cx, obj, index, &hole, &elt)) {
1213
return false;
1214
}
1215
1216
/* Get element's character string. */
1217
JSString* str;
1218
if (hole) {
1219
str = cx->runtime()->emptyString;
1220
} else {
1221
str = ValueToSource(cx, elt);
1222
if (!str) {
1223
return false;
1224
}
1225
}
1226
1227
/* Append element to buffer. */
1228
if (!sb.append(str)) {
1229
return false;
1230
}
1231
if (index + 1 != length) {
1232
if (!sb.append(", ")) {
1233
return false;
1234
}
1235
} else if (hole) {
1236
if (!sb.append(',')) {
1237
return false;
1238
}
1239
}
1240
}
1241
1242
/* Finalize the buffer. */
1243
if (!sb.append(']')) {
1244
return false;
1245
}
1246
1247
make_string:
1248
JSString* str = sb.finishString();
1249
if (!str) {
1250
return false;
1251
}
1252
1253
args.rval().setString(str);
1254
return true;
1255
}
1256
1257
struct EmptySeparatorOp {
1258
bool operator()(JSContext*, StringBuffer& sb) { return true; }
1259
};
1260
1261
template <typename CharT>
1262
struct CharSeparatorOp {
1263
const CharT sep;
1264
explicit CharSeparatorOp(CharT sep) : sep(sep) {}
1265
bool operator()(JSContext*, StringBuffer& sb) { return sb.append(sep); }
1266
};
1267
1268
struct StringSeparatorOp {
1269
HandleLinearString sep;
1270
1271
explicit StringSeparatorOp(HandleLinearString sep) : sep(sep) {}
1272
1273
bool operator()(JSContext* cx, StringBuffer& sb) { return sb.append(sep); }
1274
};
1275
1276
template <typename SeparatorOp>
1277
static bool ArrayJoinDenseKernel(JSContext* cx, SeparatorOp sepOp,
1278
HandleNativeObject obj, uint64_t length,
1279
StringBuffer& sb, uint32_t* numProcessed) {
1280
// This loop handles all elements up to initializedLength. If
1281
// length > initLength we rely on the second loop to add the
1282
// other elements.
1283
MOZ_ASSERT(*numProcessed == 0);
1284
uint64_t initLength =
1285
std::min<uint64_t>(obj->getDenseInitializedLength(), length);
1286
MOZ_ASSERT(initLength <= UINT32_MAX,
1287
"initialized length shouldn't exceed UINT32_MAX");
1288
uint32_t initLengthClamped = uint32_t(initLength);
1289
while (*numProcessed < initLengthClamped) {
1290
if (!CheckForInterrupt(cx)) {
1291
return false;
1292
}
1293
1294
// Step 7.b.
1295
Value elem = obj->getDenseElement(*numProcessed);
1296
1297
// Steps 7.c-d.
1298
if (elem.isString()) {
1299
if (!sb.append(elem.toString())) {
1300
return false;
1301
}
1302
} else if (elem.isNumber()) {
1303
if (!NumberValueToStringBuffer(cx, elem, sb)) {
1304
return false;
1305
}
1306
} else if (elem.isBoolean()) {
1307
if (!BooleanToStringBuffer(elem.toBoolean(), sb)) {
1308
return false;
1309
}
1310
} else if (elem.isObject() || elem.isSymbol()) {
1311
/*
1312
* Object stringifying could modify the initialized length or make
1313
* the array sparse. Delegate it to a separate loop to keep this
1314
* one tight.
1315
*
1316
* Symbol stringifying is a TypeError, so into the slow path
1317
* with those as well.
1318
*/
1319
break;
1320
} else if (elem.isBigInt()) {
1321
// ToString(bigint) doesn't access bigint.toString or
1322
// anything like that, so it can't mutate the array we're
1323
// walking through, so it *could* be handled here. We don't
1324
// do so yet for reasons of initial-implementation economy.
1325
break;
1326
} else {
1327
MOZ_ASSERT(elem.isMagic(JS_ELEMENTS_HOLE) || elem.isNullOrUndefined());
1328
}
1329
1330
// Steps 7.a, 7.e.
1331
if (++(*numProcessed) != length && !sepOp(cx, sb)) {
1332
return false;
1333
}
1334
}
1335
1336
return true;
1337
}
1338
1339
template <typename SeparatorOp>
1340
static bool ArrayJoinKernel(JSContext* cx, SeparatorOp sepOp, HandleObject obj,
1341
uint64_t length, StringBuffer& sb) {
1342
// Step 6.
1343
uint32_t numProcessed = 0;
1344
1345
if (IsPackedArrayOrNoExtraIndexedProperties(obj, length)) {
1346
if (!ArrayJoinDenseKernel<SeparatorOp>(cx, sepOp, obj.as<NativeObject>(),
1347
length, sb, &numProcessed)) {
1348
return false;
1349
}
1350
}
1351
1352
// Step 7.
1353
if (numProcessed != length) {
1354
RootedValue v(cx);
1355
for (uint64_t i = numProcessed; i < length;) {
1356
if (!CheckForInterrupt(cx)) {
1357
return false;
1358
}
1359
1360
// Step 7.b.
1361
if (!GetArrayElement(cx, obj, i, &v)) {
1362
return false;
1363
}
1364
1365
// Steps 7.c-d.
1366
if (!v.isNullOrUndefined()) {
1367
if (!ValueToStringBuffer(cx, v, sb)) {
1368
return false;
1369
}
1370
}
1371
1372
// Steps 7.a, 7.e.
1373
if (++i != length && !sepOp(cx, sb)) {
1374
return false;
1375
}
1376
}
1377
}
1378
1379
return true;
1380
}
1381
1382
// ES2017 draft rev 1b0184bc17fc09a8ddcf4aeec9b6d9fcac4eafce
1383
// 22.1.3.13 Array.prototype.join ( separator )
1384
bool js::array_join(JSContext* cx, unsigned argc, Value* vp) {
1385
if (!CheckRecursionLimit(cx)) {
1386
return false;
1387
}
1388
1389
AutoGeckoProfilerEntry pseudoFrame(
1390
cx, "Array.prototype.join", JS::ProfilingCategoryPair::JS,
1391
uint32_t(ProfilingStackFrame::Flags::RELEVANT_FOR_JS));
1392
CallArgs args = CallArgsFromVp(argc, vp);
1393
1394
// Step 1.
1395
RootedObject obj(cx, ToObject(cx, args.thisv()));
1396
if (!obj) {
1397
return false;
1398
}
1399
1400
AutoCycleDetector detector(cx, obj);
1401
if (!detector.init()) {
1402
return false;
1403
}
1404
1405
if (detector.foundCycle()) {
1406
args.rval().setString(cx->names().empty);
1407
return true;
1408
}
1409
1410
// Step 2.
1411
uint64_t length;
1412
if (!GetLengthProperty(cx, obj, &length)) {
1413
return false;
1414
}
1415
1416
// Steps 3-4.
1417
RootedLinearString sepstr(cx);
1418
if (args.hasDefined(0)) {
1419
JSString* s = ToString<CanGC>(cx, args[0]);
1420
if (!s) {
1421
return false;
1422
}
1423
sepstr = s->ensureLinear(cx);
1424
if (!sepstr) {
1425
return false;
1426
}
1427
} else {
1428
sepstr = cx->names().comma;
1429
}
1430
1431
// Steps 5-8 (When the length is zero, directly return the empty string).
1432
if (length == 0) {
1433
args.rval().setString(cx->emptyString());
1434
return true;
1435
}
1436
1437
// An optimized version of a special case of steps 5-8: when length==1 and
1438
// the 0th element is a string, ToString() of that element is a no-op and
1439
// so it can be immediately returned as the result.
1440
if (length == 1 && obj->isNative()) {
1441
NativeObject* nobj = &obj->as<NativeObject>();
1442
if (nobj->getDenseInitializedLength() == 1) {
1443
Value elem0 = nobj->getDenseElement(0);
1444
if (elem0.isString()) {
1445
args.rval().set(elem0);
1446
return true;
1447
}
1448
}
1449
}
1450
1451
// Step 5.
1452
JSStringBuilder sb(cx);
1453
if (sepstr->hasTwoByteChars() && !sb.ensureTwoByteChars()) {
1454
return false;
1455
}
1456
1457
// The separator will be added |length - 1| times, reserve space for that
1458
// so that we don't have to unnecessarily grow the buffer.
1459
size_t seplen = sepstr->length();
1460
if (seplen > 0) {
1461
if (length > UINT32_MAX) {
1462
ReportAllocationOverflow(cx);
1463
return false;
1464
}
1465
CheckedInt<uint32_t> res =
1466
CheckedInt<uint32_t>(seplen) * (uint32_t(length) - 1);
1467
if (!res.isValid()) {
1468
ReportAllocationOverflow(cx);
1469
return false;
1470
}
1471
1472
if (!sb.reserve(res.value())) {
1473
return false;
1474
}
1475
}
1476
1477
// Various optimized versions of steps 6-7.
1478
if (seplen == 0) {
1479
EmptySeparatorOp op;
1480
if (!ArrayJoinKernel(cx, op, obj, length, sb)) {
1481
return false;
1482
}
1483
} else if (seplen == 1) {
1484
char16_t c = sepstr->latin1OrTwoByteChar(0);
1485
if (c <= JSString::MAX_LATIN1_CHAR) {
1486
CharSeparatorOp<Latin1Char> op(c);
1487
if (!ArrayJoinKernel(cx, op, obj, length, sb)) {
1488
return false;
1489
}
1490
} else {
1491
CharSeparatorOp<char16_t> op(c);
1492
if (!ArrayJoinKernel(cx, op, obj, length, sb)) {
1493
return false;
1494
}
1495
}
1496
} else {
1497
StringSeparatorOp op(sepstr);
1498
if (!ArrayJoinKernel(cx, op, obj, length, sb)) {
1499
return false;
1500
}
1501
}
1502
1503
// Step 8.
1504
JSString* str = sb.finishString();
1505
if (!str) {
1506
return false;
1507
}
1508
1509
args.rval().setString(str);
1510
return true;
1511
}
1512
1513
// ES2017 draft rev f8a9be8ea4bd97237d176907a1e3080dce20c68f
1514
// 22.1.3.27 Array.prototype.toLocaleString ([ reserved1 [ , reserved2 ] ])
1515
// ES2017 Intl draft rev 78bbe7d1095f5ff3760ac4017ed366026e4cb276
1516
// 13.4.1 Array.prototype.toLocaleString ([ locales [ , options ]])
1517
static bool array_toLocaleString(JSContext* cx, unsigned argc, Value* vp) {
1518
if (!CheckRecursionLimit(cx)) {
1519
return false;
1520
}
1521
1522
CallArgs args = CallArgsFromVp(argc, vp);
1523
1524
// Step 1
1525
RootedObject obj(cx, ToObject(cx, args.thisv()));
1526
if (!obj) {
1527
return false;
1528
}
1529
1530
// Avoid calling into self-hosted code if the array is empty.
1531
if (obj->is<ArrayObject>() && obj->as<ArrayObject>().length() == 0) {
1532
args.rval().setString(cx->names().empty);
1533
return true;
1534
}
1535
1536
AutoCycleDetector detector(cx, obj);
1537
if (!detector.init()) {
1538
return false;
1539
}
1540
1541
if (detector.foundCycle()) {
1542
args.rval().setString(cx->names().empty);
1543
return true;
1544
}
1545
1546
FixedInvokeArgs<2> args2(cx);
1547
1548
args2[0].set(args.get(0));
1549
args2[1].set(args.get(1));
1550
1551
// Steps 2-10.
1552
RootedValue thisv(cx, ObjectValue(*obj));
1553
return CallSelfHostedFunction(cx, cx->names().ArrayToLocaleString, thisv,
1554
args2, args.rval());
1555
}
1556
1557
/* vector must point to rooted memory. */
1558
static bool SetArrayElements(
1559
JSContext* cx, HandleObject obj, uint64_t start, uint32_t count,
1560
const Value* vector,
1561
ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update) {
1562
MOZ_ASSERT(count <= MAX_ARRAY_INDEX);
1563
MOZ_ASSERT(start + count < uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT));
1564
1565
if (count == 0) {
1566
return true;
1567
}
1568
1569
if (!ObjectMayHaveExtraIndexedProperties(obj) && start <= UINT32_MAX) {
1570
NativeObject* nobj = &obj->as<NativeObject>();
1571
DenseElementResult result = nobj->setOrExtendDenseElements(
1572
cx, uint32_t(start), vector, count, updateTypes);
1573
if (result != DenseElementResult::Incomplete) {
1574
return result == DenseElementResult::Success;
1575
}
1576
}
1577
1578
RootedId id(cx);
1579
const Value* end = vector + count;
1580
while (vector < end) {
1581
if (!CheckForInterrupt(cx)) {
1582
return false;
1583
}
1584
1585
if (!ToId(cx, start++, &id)) {
1586
return false;
1587
}
1588
1589
if (!SetProperty(cx, obj, id, HandleValue::fromMarkedLocation(vector++))) {
1590
return false;
1591
}
1592
}
1593
1594
return true;
1595
}
1596
1597
static DenseElementResult ArrayReverseDenseKernel(JSContext* cx,
1598
HandleNativeObject obj,
1599
uint32_t length) {
1600
MOZ_ASSERT(length > 1);
1601
1602
// If there are no elements, we're done.
1603
if (obj->getDenseInitializedLength() == 0) {
1604
return DenseElementResult::Success;
1605
}
1606
1607
if (!obj->isExtensible()) {
1608
return DenseElementResult::Incomplete;
1609
}
1610
1611
if (!IsPackedArray(obj)) {
1612
/*
1613
* It's actually surprisingly complicated to reverse an array due
1614
* to the orthogonality of array length and array capacity while
1615
* handling leading and trailing holes correctly. Reversing seems
1616
* less likely to be a common operation than other array
1617
* mass-mutation methods, so for now just take a probably-small
1618
* memory hit (in the absence of too many holes in the array at
1619
* its start) and ensure that the capacity is sufficient to hold
1620
* all the elements in the array if it were full.
1621
*/
1622
DenseElementResult result = obj->ensureDenseElements(cx, length, 0);
1623
if (result != DenseElementResult::Success) {
1624
return result;
1625
}
1626
1627
/* Fill out the array's initialized length to its proper length. */
1628
obj->ensureDenseInitializedLength(cx, length, 0);
1629
} else {
1630
if (!obj->maybeCopyElementsForWrite(cx)) {
1631
return DenseElementResult::Failure;
1632
}
1633
}
1634
1635
if (!MaybeInIteration(obj, cx) && !cx->zone()->needsIncrementalBarrier()) {
1636
obj->reverseDenseElementsNoPreBarrier(length);
1637
return DenseElementResult::Success;
1638
}
1639
1640
RootedValue origlo(cx), orighi(cx);
1641
1642
uint32_t lo = 0, hi = length - 1;
1643
for (; lo < hi; lo++, hi--) {
1644
origlo = obj->getDenseElement(lo);
1645
orighi = obj->getDenseElement(hi);
1646
obj->setDenseElement(lo, orighi);
1647
if (orighi.isMagic(JS_ELEMENTS_HOLE) &&
1648
!SuppressDeletedProperty(cx, obj, INT_TO_JSID(lo))) {
1649
return DenseElementResult::Failure;
1650
}
1651
obj->setDenseElement(hi, origlo);
1652
if (origlo.isMagic(JS_ELEMENTS_HOLE) &&
1653
!SuppressDeletedProperty(cx, obj, INT_TO_JSID(hi))) {
1654
return DenseElementResult::Failure;
1655
}
1656
}
1657
1658
return DenseElementResult::Success;
1659
}
1660
1661
// ES2017 draft rev 1b0184bc17fc09a8ddcf4aeec9b6d9fcac4eafce
1662
// 22.1.3.21 Array.prototype.reverse ( )
1663
bool js::array_reverse(JSContext* cx, unsigned argc, Value* vp) {
1664
AutoGeckoProfilerEntry pseudoFrame(
1665
cx, "Array.prototype.reverse", JS::ProfilingCategoryPair::JS,
1666
uint32_t(ProfilingStackFrame::Flags::RELEVANT_FOR_JS));
1667
CallArgs args = CallArgsFromVp(argc, vp);
1668
1669
// Step 1.
1670
RootedObject obj(cx, ToObject(cx, args.thisv()));
1671
if (!obj) {
1672
return false;
1673
}
1674
1675
// Step 2.
1676
uint64_t len;
1677
if (!GetLengthProperty(cx, obj, &len)) {
1678
return false;
1679
}
1680
1681
// An empty array or an array with length 1 is already reversed.
1682
if (len <= 1) {
1683
args.rval().setObject(*obj);
1684
return true;
1685
}
1686
1687
if (IsPackedArrayOrNoExtraIndexedProperties(obj, len) && len <= UINT32_MAX) {
1688
DenseElementResult result =
1689
ArrayReverseDenseKernel(cx, obj.as<NativeObject>(), uint32_t(len));
1690
if (result != DenseElementResult::Incomplete) {
1691
/*
1692
* Per ECMA-262, don't update the length of the array, even if the new
1693
* array has trailing holes (and thus the original array began with
1694
* holes).
1695
*/
1696
args.rval().setObject(*obj);
1697
return result == DenseElementResult::Success;
1698
}
1699
}
1700
1701
// Steps 3-5.
1702
RootedValue lowval(cx), hival(cx);
1703
for (uint64_t i = 0, half = len / 2; i < half; i++) {
1704
bool hole, hole2;
1705
if (!CheckForInterrupt(cx) ||
1706
!HasAndGetElement(cx, obj, i, &hole, &lowval) ||
1707
!HasAndGetElement(cx, obj, len - i - 1, &hole2, &hival)) {
1708
return false;
1709
}
1710
1711
if (!hole && !hole2) {
1712
if (!SetArrayElement(cx, obj, i, hival)) {
1713
return false;
1714
}
1715
if (!SetArrayElement(cx, obj, len - i - 1, lowval)) {
1716
return false;
1717
}
1718
} else if (hole && !hole2) {
1719
if (!SetArrayElement(cx, obj, i, hival)) {
1720
return false;
1721
}