Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sts=4 et sw=4 tw=99:
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/. */
7 #include "vm/ArrayBufferObject.h"
9 #include "mozilla/Alignment.h"
10 #include "mozilla/FloatingPoint.h"
11 #include "mozilla/PodOperations.h"
13 #include <string.h>
14 #ifndef XP_WIN
15 # include <sys/mman.h>
16 #endif
18 #ifdef MOZ_VALGRIND
19 # include <valgrind/memcheck.h>
20 #endif
22 #include "jsapi.h"
23 #include "jsarray.h"
24 #include "jscntxt.h"
25 #include "jscpucfg.h"
26 #include "jsnum.h"
27 #include "jsobj.h"
28 #include "jstypes.h"
29 #include "jsutil.h"
30 #ifdef XP_WIN
31 # include "jswin.h"
32 #endif
33 #include "jswrapper.h"
35 #include "gc/Barrier.h"
36 #include "gc/Marking.h"
37 #include "gc/Memory.h"
38 #include "jit/AsmJS.h"
39 #include "jit/AsmJSModule.h"
40 #include "js/MemoryMetrics.h"
41 #include "vm/GlobalObject.h"
42 #include "vm/Interpreter.h"
43 #include "vm/NumericConversions.h"
44 #include "vm/SharedArrayObject.h"
45 #include "vm/WrapperObject.h"
47 #include "jsatominlines.h"
48 #include "jsinferinlines.h"
49 #include "jsobjinlines.h"
51 #include "vm/Shape-inl.h"
53 using mozilla::DebugOnly;
55 using namespace js;
56 using namespace js::gc;
57 using namespace js::types;
59 /*
60 * Convert |v| to an array index for an array of length |length| per
61 * the Typed Array Specification section 7.0, |subarray|. If successful,
62 * the output value is in the range [0, length].
63 */
64 bool
65 js::ToClampedIndex(JSContext *cx, HandleValue v, uint32_t length, uint32_t *out)
66 {
67 int32_t result;
68 if (!ToInt32(cx, v, &result))
69 return false;
70 if (result < 0) {
71 result += length;
72 if (result < 0)
73 result = 0;
74 } else if (uint32_t(result) > length) {
75 result = length;
76 }
77 *out = uint32_t(result);
78 return true;
79 }
81 /*
82 * ArrayBufferObject
83 *
84 * This class holds the underlying raw buffer that the TypedArrayObject classes
85 * access. It can be created explicitly and passed to a TypedArrayObject, or
86 * can be created implicitly by constructing a TypedArrayObject with a size.
87 */
89 /*
90 * ArrayBufferObject (base)
91 */
93 const Class ArrayBufferObject::protoClass = {
94 "ArrayBufferPrototype",
95 JSCLASS_HAS_CACHED_PROTO(JSProto_ArrayBuffer),
96 JS_PropertyStub, /* addProperty */
97 JS_DeletePropertyStub, /* delProperty */
98 JS_PropertyStub, /* getProperty */
99 JS_StrictPropertyStub, /* setProperty */
100 JS_EnumerateStub,
101 JS_ResolveStub,
102 JS_ConvertStub
103 };
105 const Class ArrayBufferObject::class_ = {
106 "ArrayBuffer",
107 JSCLASS_IMPLEMENTS_BARRIERS |
108 JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) |
109 JSCLASS_HAS_CACHED_PROTO(JSProto_ArrayBuffer) |
110 JSCLASS_BACKGROUND_FINALIZE,
111 JS_PropertyStub, /* addProperty */
112 JS_DeletePropertyStub, /* delProperty */
113 JS_PropertyStub, /* getProperty */
114 JS_StrictPropertyStub, /* setProperty */
115 JS_EnumerateStub,
116 JS_ResolveStub,
117 JS_ConvertStub,
118 ArrayBufferObject::finalize,
119 nullptr, /* call */
120 nullptr, /* hasInstance */
121 nullptr, /* construct */
122 ArrayBufferObject::obj_trace,
123 JS_NULL_CLASS_SPEC,
124 JS_NULL_CLASS_EXT
125 };
127 const JSFunctionSpec ArrayBufferObject::jsfuncs[] = {
128 JS_FN("slice", ArrayBufferObject::fun_slice, 2, JSFUN_GENERIC_NATIVE),
129 JS_FS_END
130 };
132 const JSFunctionSpec ArrayBufferObject::jsstaticfuncs[] = {
133 JS_FN("isView", ArrayBufferObject::fun_isView, 1, 0),
134 JS_FS_END
135 };
137 bool
138 js::IsArrayBuffer(HandleValue v)
139 {
140 return v.isObject() &&
141 (v.toObject().is<ArrayBufferObject>() ||
142 v.toObject().is<SharedArrayBufferObject>());
143 }
145 bool
146 js::IsArrayBuffer(HandleObject obj)
147 {
148 return obj->is<ArrayBufferObject>() || obj->is<SharedArrayBufferObject>();
149 }
151 bool
152 js::IsArrayBuffer(JSObject *obj)
153 {
154 return obj->is<ArrayBufferObject>() || obj->is<SharedArrayBufferObject>();
155 }
157 ArrayBufferObject &
158 js::AsArrayBuffer(HandleObject obj)
159 {
160 JS_ASSERT(IsArrayBuffer(obj));
161 if (obj->is<SharedArrayBufferObject>())
162 return obj->as<SharedArrayBufferObject>();
163 return obj->as<ArrayBufferObject>();
164 }
166 ArrayBufferObject &
167 js::AsArrayBuffer(JSObject *obj)
168 {
169 JS_ASSERT(IsArrayBuffer(obj));
170 if (obj->is<SharedArrayBufferObject>())
171 return obj->as<SharedArrayBufferObject>();
172 return obj->as<ArrayBufferObject>();
173 }
175 MOZ_ALWAYS_INLINE bool
176 ArrayBufferObject::byteLengthGetterImpl(JSContext *cx, CallArgs args)
177 {
178 JS_ASSERT(IsArrayBuffer(args.thisv()));
179 args.rval().setInt32(args.thisv().toObject().as<ArrayBufferObject>().byteLength());
180 return true;
181 }
183 bool
184 ArrayBufferObject::byteLengthGetter(JSContext *cx, unsigned argc, Value *vp)
185 {
186 CallArgs args = CallArgsFromVp(argc, vp);
187 return CallNonGenericMethod<IsArrayBuffer, byteLengthGetterImpl>(cx, args);
188 }
190 bool
191 ArrayBufferObject::fun_slice_impl(JSContext *cx, CallArgs args)
192 {
193 JS_ASSERT(IsArrayBuffer(args.thisv()));
195 Rooted<ArrayBufferObject*> thisObj(cx, &args.thisv().toObject().as<ArrayBufferObject>());
197 // these are the default values
198 uint32_t length = thisObj->byteLength();
199 uint32_t begin = 0, end = length;
201 if (args.length() > 0) {
202 if (!ToClampedIndex(cx, args[0], length, &begin))
203 return false;
205 if (args.length() > 1) {
206 if (!ToClampedIndex(cx, args[1], length, &end))
207 return false;
208 }
209 }
211 if (begin > end)
212 begin = end;
214 JSObject *nobj = createSlice(cx, thisObj, begin, end);
215 if (!nobj)
216 return false;
217 args.rval().setObject(*nobj);
218 return true;
219 }
221 bool
222 ArrayBufferObject::fun_slice(JSContext *cx, unsigned argc, Value *vp)
223 {
224 CallArgs args = CallArgsFromVp(argc, vp);
225 return CallNonGenericMethod<IsArrayBuffer, fun_slice_impl>(cx, args);
226 }
228 /*
229 * ArrayBuffer.isView(obj); ES6 (Dec 2013 draft) 24.1.3.1
230 */
231 bool
232 ArrayBufferObject::fun_isView(JSContext *cx, unsigned argc, Value *vp)
233 {
234 CallArgs args = CallArgsFromVp(argc, vp);
235 args.rval().setBoolean(args.get(0).isObject() &&
236 JS_IsArrayBufferViewObject(&args.get(0).toObject()));
237 return true;
238 }
240 /*
241 * new ArrayBuffer(byteLength)
242 */
243 bool
244 ArrayBufferObject::class_constructor(JSContext *cx, unsigned argc, Value *vp)
245 {
246 int32_t nbytes = 0;
247 CallArgs args = CallArgsFromVp(argc, vp);
248 if (argc > 0 && !ToInt32(cx, args[0], &nbytes))
249 return false;
251 if (nbytes < 0) {
252 /*
253 * We're just not going to support arrays that are bigger than what will fit
254 * as an integer value; if someone actually ever complains (validly), then we
255 * can fix.
256 */
257 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
258 return false;
259 }
261 JSObject *bufobj = create(cx, uint32_t(nbytes));
262 if (!bufobj)
263 return false;
264 args.rval().setObject(*bufobj);
265 return true;
266 }
268 /*
269 * Note that some callers are allowed to pass in a nullptr cx, so we allocate
270 * with the cx if available and fall back to the runtime. If oldptr is given,
271 * it's expected to be a previously-allocated contents pointer that we then
272 * realloc.
273 */
274 static void *
275 AllocateArrayBufferContents(JSContext *maybecx, uint32_t nbytes, void *oldptr = nullptr, size_t oldnbytes = 0)
276 {
277 void *p;
279 // if oldptr is given, then we need to do a realloc
280 if (oldptr) {
281 p = maybecx ? maybecx->runtime()->reallocCanGC(oldptr, nbytes) : js_realloc(oldptr, nbytes);
283 // if we grew the array, we need to set the new bytes to 0
284 if (p && nbytes > oldnbytes)
285 memset(reinterpret_cast<uint8_t *>(p) + oldnbytes, 0, nbytes - oldnbytes);
286 } else {
287 p = maybecx ? maybecx->runtime()->callocCanGC(nbytes) : js_calloc(nbytes);
288 }
290 if (!p && maybecx)
291 js_ReportOutOfMemory(maybecx);
293 return p;
294 }
296 ArrayBufferViewObject *
297 ArrayBufferObject::viewList() const
298 {
299 return reinterpret_cast<ArrayBufferViewObject *>(getSlot(VIEW_LIST_SLOT).toPrivate());
300 }
302 void
303 ArrayBufferObject::setViewListNoBarrier(ArrayBufferViewObject *viewsHead)
304 {
305 setSlot(VIEW_LIST_SLOT, PrivateValue(viewsHead));
306 }
308 void
309 ArrayBufferObject::setViewList(ArrayBufferViewObject *viewsHead)
310 {
311 if (ArrayBufferViewObject *oldHead = viewList())
312 ArrayBufferViewObject::writeBarrierPre(oldHead);
313 setViewListNoBarrier(viewsHead);
314 PostBarrierTypedArrayObject(this);
315 }
317 bool
318 ArrayBufferObject::canNeuter(JSContext *cx)
319 {
320 if (isSharedArrayBuffer())
321 return false;
323 if (isAsmJSArrayBuffer()) {
324 if (!ArrayBufferObject::canNeuterAsmJSArrayBuffer(cx, *this))
325 return false;
326 }
328 return true;
329 }
331 /* static */ void
332 ArrayBufferObject::neuter(JSContext *cx, Handle<ArrayBufferObject*> buffer, void *newData)
333 {
334 JS_ASSERT(buffer->canNeuter(cx));
336 // Neuter all views on the buffer, clear out the list of views and the
337 // buffer's data.
339 for (ArrayBufferViewObject *view = buffer->viewList(); view; view = view->nextView()) {
340 view->neuter(newData);
342 // Notify compiled jit code that the base pointer has moved.
343 MarkObjectStateChange(cx, view);
344 }
346 if (newData != buffer->dataPointer())
347 buffer->setNewOwnedData(cx->runtime()->defaultFreeOp(), newData);
349 buffer->setByteLength(0);
350 buffer->setViewList(nullptr);
351 buffer->setIsNeutered();
353 // If this is happening during an incremental GC, remove the buffer from
354 // the list of live buffers with multiple views if necessary.
355 if (buffer->inLiveList()) {
356 ArrayBufferVector &gcLiveArrayBuffers = cx->compartment()->gcLiveArrayBuffers;
357 DebugOnly<bool> found = false;
358 for (size_t i = 0; i < gcLiveArrayBuffers.length(); i++) {
359 if (buffer == gcLiveArrayBuffers[i]) {
360 found = true;
361 gcLiveArrayBuffers[i] = gcLiveArrayBuffers.back();
362 gcLiveArrayBuffers.popBack();
363 break;
364 }
365 }
366 JS_ASSERT(found);
367 buffer->setInLiveList(false);
368 }
369 }
371 void
372 ArrayBufferObject::setNewOwnedData(FreeOp* fop, void *newData)
373 {
374 JS_ASSERT(!isAsmJSArrayBuffer());
375 JS_ASSERT(!isSharedArrayBuffer());
377 if (ownsData()) {
378 JS_ASSERT(newData != dataPointer());
379 releaseData(fop);
380 }
382 setDataPointer(static_cast<uint8_t *>(newData), OwnsData);
383 }
385 void
386 ArrayBufferObject::changeContents(JSContext *cx, void *newData)
387 {
388 // Change buffer contents.
389 uint8_t* oldDataPointer = dataPointer();
390 setNewOwnedData(cx->runtime()->defaultFreeOp(), newData);
392 // Update all views.
393 ArrayBufferViewObject *viewListHead = viewList();
394 for (ArrayBufferViewObject *view = viewListHead; view; view = view->nextView()) {
395 // Watch out for NULL data pointers in views. This means that the view
396 // is not fully initialized (in which case it'll be initialized later
397 // with the correct pointer).
398 uint8_t *viewDataPointer = view->dataPointer();
399 if (viewDataPointer) {
400 JS_ASSERT(newData);
401 ptrdiff_t offset = viewDataPointer - oldDataPointer;
402 viewDataPointer = static_cast<uint8_t *>(newData) + offset;
403 view->setPrivate(viewDataPointer);
404 }
406 // Notify compiled jit code that the base pointer has moved.
407 MarkObjectStateChange(cx, view);
408 }
409 }
411 #if defined(JS_CPU_X64)
412 // Refer to comment above AsmJSMappedSize in AsmJS.h.
413 JS_STATIC_ASSERT(AsmJSAllocationGranularity == AsmJSPageSize);
414 #endif
416 #if defined(JS_ION) && defined(JS_CPU_X64)
417 /* static */ bool
418 ArrayBufferObject::prepareForAsmJS(JSContext *cx, Handle<ArrayBufferObject*> buffer)
419 {
420 if (buffer->isAsmJSArrayBuffer())
421 return true;
423 // SharedArrayBuffers are already created with AsmJS support in mind.
424 if (buffer->isSharedArrayBuffer())
425 return true;
427 // Get the entire reserved region (with all pages inaccessible).
428 void *data;
429 # ifdef XP_WIN
430 data = VirtualAlloc(nullptr, AsmJSMappedSize, MEM_RESERVE, PAGE_NOACCESS);
431 if (!data)
432 return false;
433 # else
434 data = mmap(nullptr, AsmJSMappedSize, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
435 if (data == MAP_FAILED)
436 return false;
437 # endif
439 // Enable access to the valid region.
440 JS_ASSERT(buffer->byteLength() % AsmJSAllocationGranularity == 0);
441 # ifdef XP_WIN
442 if (!VirtualAlloc(data, buffer->byteLength(), MEM_COMMIT, PAGE_READWRITE)) {
443 VirtualFree(data, 0, MEM_RELEASE);
444 return false;
445 }
446 # else
447 size_t validLength = buffer->byteLength();
448 if (mprotect(data, validLength, PROT_READ | PROT_WRITE)) {
449 munmap(data, AsmJSMappedSize);
450 return false;
451 }
452 # if defined(MOZ_VALGRIND) && defined(VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE)
453 // Tell Valgrind/Memcheck to not report accesses in the inaccessible region.
454 VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE((unsigned char*)data + validLength,
455 AsmJSMappedSize-validLength);
456 # endif
457 # endif
459 // Copy over the current contents of the typed array.
460 memcpy(data, buffer->dataPointer(), buffer->byteLength());
462 // Swap the new elements into the ArrayBufferObject.
463 buffer->changeContents(cx, data);
464 JS_ASSERT(data == buffer->dataPointer());
466 // Mark the ArrayBufferObject so (1) we don't do this again, (2) we know not
467 // to js_free the data in the normal way.
468 buffer->setIsAsmJSArrayBuffer();
470 return true;
471 }
473 void
474 ArrayBufferObject::releaseAsmJSArray(FreeOp *fop)
475 {
476 void *data = dataPointer();
478 JS_ASSERT(uintptr_t(data) % AsmJSPageSize == 0);
479 # ifdef XP_WIN
480 VirtualFree(data, 0, MEM_RELEASE);
481 # else
482 munmap(data, AsmJSMappedSize);
483 # if defined(MOZ_VALGRIND) && defined(VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE)
484 // Tell Valgrind/Memcheck to recommence reporting accesses in the
485 // previously-inaccessible region.
486 if (AsmJSMappedSize > 0) {
487 VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE(data, AsmJSMappedSize);
488 }
489 # endif
490 # endif
491 }
492 #else /* defined(JS_ION) && defined(JS_CPU_X64) */
493 bool
494 ArrayBufferObject::prepareForAsmJS(JSContext *cx, Handle<ArrayBufferObject*> buffer)
495 {
496 if (buffer->isAsmJSArrayBuffer())
497 return true;
499 if (buffer->isSharedArrayBuffer())
500 return true;
502 if (!ensureNonInline(cx, buffer))
503 return false;
505 buffer->setIsAsmJSArrayBuffer();
506 return true;
507 }
509 void
510 ArrayBufferObject::releaseAsmJSArray(FreeOp *fop)
511 {
512 fop->free_(dataPointer());
513 }
514 #endif
516 bool
517 ArrayBufferObject::canNeuterAsmJSArrayBuffer(JSContext *cx, ArrayBufferObject &buffer)
518 {
519 JS_ASSERT(!buffer.isSharedArrayBuffer());
520 #ifdef JS_ION
521 AsmJSActivation *act = cx->mainThread().asmJSActivationStackFromOwnerThread();
522 for (; act; act = act->prevAsmJS()) {
523 if (act->module().maybeHeapBufferObject() == &buffer)
524 break;
525 }
526 if (!act)
527 return true;
529 return false;
530 #else
531 return true;
532 #endif
533 }
535 void *
536 ArrayBufferObject::createMappedContents(int fd, size_t offset, size_t length)
537 {
538 return AllocateMappedContent(fd, offset, length, ARRAY_BUFFER_ALIGNMENT);
539 }
541 void
542 ArrayBufferObject::releaseMappedArray()
543 {
544 if(!isMappedArrayBuffer() || isNeutered())
545 return;
547 DeallocateMappedContent(dataPointer(), byteLength());
548 }
550 void
551 ArrayBufferObject::addView(ArrayBufferViewObject *view)
552 {
553 // Note that pre-barriers are not needed here because either the list was
554 // previously empty, in which case no pointer is being overwritten, or the
555 // list was nonempty and will be made weak during this call (and weak
556 // pointers cannot violate the snapshot-at-the-beginning invariant.)
558 ArrayBufferViewObject *viewsHead = viewList();
559 if (viewsHead == nullptr) {
560 // This ArrayBufferObject will have a single view at this point, so it
561 // is a strong pointer (it will be marked during tracing.)
562 JS_ASSERT(view->nextView() == nullptr);
563 } else {
564 view->setNextView(viewsHead);
565 }
567 setViewList(view);
568 }
570 uint8_t *
571 ArrayBufferObject::dataPointer() const
572 {
573 if (isSharedArrayBuffer())
574 return (uint8_t *)this->as<SharedArrayBufferObject>().dataPointer();
575 return static_cast<uint8_t *>(getSlot(DATA_SLOT).toPrivate());
576 }
578 void
579 ArrayBufferObject::releaseData(FreeOp *fop)
580 {
581 JS_ASSERT(ownsData());
583 if (isAsmJSArrayBuffer())
584 releaseAsmJSArray(fop);
585 else if (isMappedArrayBuffer())
586 releaseMappedArray();
587 else
588 fop->free_(dataPointer());
589 }
591 void
592 ArrayBufferObject::setDataPointer(void *data, OwnsState ownsData)
593 {
594 MOZ_ASSERT_IF(!is<SharedArrayBufferObject>() && !isMappedArrayBuffer(), data != nullptr);
595 setSlot(DATA_SLOT, PrivateValue(data));
596 setOwnsData(ownsData);
597 }
599 size_t
600 ArrayBufferObject::byteLength() const
601 {
602 return size_t(getSlot(BYTE_LENGTH_SLOT).toDouble());
603 }
605 void
606 ArrayBufferObject::setByteLength(size_t length)
607 {
608 setSlot(BYTE_LENGTH_SLOT, DoubleValue(length));
609 }
611 uint32_t
612 ArrayBufferObject::flags() const
613 {
614 return uint32_t(getSlot(FLAGS_SLOT).toInt32());
615 }
617 void
618 ArrayBufferObject::setFlags(uint32_t flags)
619 {
620 setSlot(FLAGS_SLOT, Int32Value(flags));
621 }
623 ArrayBufferObject *
624 ArrayBufferObject::create(JSContext *cx, uint32_t nbytes, void *data /* = nullptr */,
625 NewObjectKind newKind /* = GenericObject */,
626 bool mapped /* = false */)
627 {
628 JS_ASSERT_IF(mapped, data);
630 // If we need to allocate data, try to use a larger object size class so
631 // that the array buffer's data can be allocated inline with the object.
632 // The extra space will be left unused by the object's fixed slots and
633 // available for the buffer's data, see NewObject().
634 size_t reservedSlots = JSCLASS_RESERVED_SLOTS(&class_);
636 size_t nslots = reservedSlots;
637 if (!data) {
638 size_t usableSlots = JSObject::MAX_FIXED_SLOTS - reservedSlots;
639 if (nbytes <= usableSlots * sizeof(Value)) {
640 int newSlots = (nbytes - 1) / sizeof(Value) + 1;
641 JS_ASSERT(int(nbytes) <= newSlots * int(sizeof(Value)));
642 nslots = reservedSlots + newSlots;
643 } else {
644 data = AllocateArrayBufferContents(cx, nbytes);
645 if (!data)
646 return nullptr;
647 }
648 }
650 JS_ASSERT(!(class_.flags & JSCLASS_HAS_PRIVATE));
651 gc::AllocKind allocKind = GetGCObjectKind(nslots);
653 Rooted<ArrayBufferObject*> obj(cx, NewBuiltinClassInstance<ArrayBufferObject>(cx, allocKind, newKind));
654 if (!obj)
655 return nullptr;
657 JS_ASSERT(obj->getClass() == &class_);
659 JS_ASSERT(!gc::IsInsideNursery(cx->runtime(), obj));
661 if (data) {
662 obj->initialize(nbytes, data, OwnsData);
663 if (mapped)
664 obj->setIsMappedArrayBuffer();
665 } else {
666 void *data = obj->fixedData(reservedSlots);
667 memset(data, 0, nbytes);
668 obj->initialize(nbytes, data, DoesntOwnData);
669 }
671 return obj;
672 }
674 JSObject *
675 ArrayBufferObject::createSlice(JSContext *cx, Handle<ArrayBufferObject*> arrayBuffer,
676 uint32_t begin, uint32_t end)
677 {
678 uint32_t bufLength = arrayBuffer->byteLength();
679 if (begin > bufLength || end > bufLength || begin > end) {
680 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPE_ERR_BAD_ARGS);
681 return nullptr;
682 }
684 uint32_t length = end - begin;
686 if (!arrayBuffer->hasData())
687 return create(cx, 0);
689 ArrayBufferObject *slice = create(cx, length);
690 if (!slice)
691 return nullptr;
692 memcpy(slice->dataPointer(), arrayBuffer->dataPointer() + begin, length);
693 return slice;
694 }
696 bool
697 ArrayBufferObject::createDataViewForThisImpl(JSContext *cx, CallArgs args)
698 {
699 JS_ASSERT(IsArrayBuffer(args.thisv()));
701 /*
702 * This method is only called for |DataView(alienBuf, ...)| which calls
703 * this as |createDataViewForThis.call(alienBuf, ..., DataView.prototype)|,
704 * ergo there must be at least two arguments.
705 */
706 JS_ASSERT(args.length() >= 2);
708 Rooted<JSObject*> proto(cx, &args[args.length() - 1].toObject());
710 Rooted<JSObject*> buffer(cx, &args.thisv().toObject());
712 /*
713 * Pop off the passed-along prototype and delegate to normal DataViewObject
714 * construction.
715 */
716 CallArgs frobbedArgs = CallArgsFromVp(args.length() - 1, args.base());
717 return DataViewObject::construct(cx, buffer, frobbedArgs, proto);
718 }
720 bool
721 ArrayBufferObject::createDataViewForThis(JSContext *cx, unsigned argc, Value *vp)
722 {
723 CallArgs args = CallArgsFromVp(argc, vp);
724 return CallNonGenericMethod<IsArrayBuffer, createDataViewForThisImpl>(cx, args);
725 }
727 /* static */ bool
728 ArrayBufferObject::ensureNonInline(JSContext *cx, Handle<ArrayBufferObject*> buffer)
729 {
730 if (!buffer->ownsData()) {
731 void *data = AllocateArrayBufferContents(cx, buffer->byteLength());
732 if (!data)
733 return false;
734 memcpy(data, buffer->dataPointer(), buffer->byteLength());
735 buffer->changeContents(cx, data);
736 }
738 return true;
739 }
741 /* static */ void *
742 ArrayBufferObject::stealContents(JSContext *cx, Handle<ArrayBufferObject*> buffer)
743 {
744 if (!buffer->canNeuter(cx)) {
745 js_ReportOverRecursed(cx);
746 return nullptr;
747 }
749 void *oldData = buffer->dataPointer();
750 void *newData = AllocateArrayBufferContents(cx, buffer->byteLength());
751 if (!newData)
752 return nullptr;
754 if (buffer->hasStealableContents()) {
755 buffer->setOwnsData(DoesntOwnData);
756 ArrayBufferObject::neuter(cx, buffer, newData);
757 return oldData;
758 } else {
759 memcpy(newData, oldData, buffer->byteLength());
760 ArrayBufferObject::neuter(cx, buffer, oldData);
761 return newData;
762 }
764 return oldData;
765 }
767 /* static */ void
768 ArrayBufferObject::addSizeOfExcludingThis(JSObject *obj, mozilla::MallocSizeOf mallocSizeOf, JS::ObjectsExtraSizes *sizes)
769 {
770 ArrayBufferObject &buffer = AsArrayBuffer(obj);
772 if (!buffer.ownsData())
773 return;
775 if (MOZ_UNLIKELY(buffer.isAsmJSArrayBuffer())) {
776 #if defined (JS_CPU_X64)
777 // On x64, ArrayBufferObject::prepareForAsmJS switches the
778 // ArrayBufferObject to use mmap'd storage.
779 sizes->nonHeapElementsAsmJS += buffer.byteLength();
780 #else
781 sizes->mallocHeapElementsAsmJS += mallocSizeOf(buffer.dataPointer());
782 #endif
783 } else if (MOZ_UNLIKELY(buffer.isMappedArrayBuffer())) {
784 sizes->nonHeapElementsMapped += buffer.byteLength();
785 } else if (buffer.dataPointer()) {
786 sizes->mallocHeapElementsNonAsmJS += mallocSizeOf(buffer.dataPointer());
787 }
788 }
790 /* static */ void
791 ArrayBufferObject::finalize(FreeOp *fop, JSObject *obj)
792 {
793 ArrayBufferObject &buffer = obj->as<ArrayBufferObject>();
795 if (buffer.ownsData())
796 buffer.releaseData(fop);
797 }
799 /* static */ void
800 ArrayBufferObject::obj_trace(JSTracer *trc, JSObject *obj)
801 {
802 if (!IS_GC_MARKING_TRACER(trc) && !trc->runtime()->isHeapMinorCollecting())
803 return;
805 // ArrayBufferObjects need to maintain a list of possibly-weak pointers to
806 // their views. The straightforward way to update the weak pointers would
807 // be in the views' finalizers, but giving views finalizers means they
808 // cannot be swept in the background. This results in a very high
809 // performance cost. Instead, ArrayBufferObjects with a single view hold a
810 // strong pointer to the view. This can entrain garbage when the single
811 // view becomes otherwise unreachable while the buffer is still live, but
812 // this is expected to be rare. ArrayBufferObjects with 0-1 views are
813 // expected to be by far the most common cases. ArrayBufferObjects with
814 // multiple views are collected into a linked list during collection, and
815 // then swept to prune out their dead views.
817 ArrayBufferObject &buffer = AsArrayBuffer(obj);
818 ArrayBufferViewObject *viewsHead = buffer.viewList();
819 if (!viewsHead)
820 return;
822 buffer.setViewList(UpdateObjectIfRelocated(trc->runtime(), &viewsHead));
824 if (viewsHead->nextView() == nullptr) {
825 // Single view: mark it, but only if we're actually doing a GC pass
826 // right now. Otherwise, the tracing pass for barrier verification will
827 // fail if we add another view and the pointer becomes weak.
828 MarkObjectUnbarriered(trc, &viewsHead, "arraybuffer.singleview");
829 buffer.setViewListNoBarrier(viewsHead);
830 } else {
831 // Multiple views: do not mark, but append buffer to list.
832 ArrayBufferVector &gcLiveArrayBuffers = buffer.compartment()->gcLiveArrayBuffers;
834 // obj_trace may be called multiple times before sweep(), so avoid
835 // adding this buffer to the list multiple times.
836 if (buffer.inLiveList()) {
837 #ifdef DEBUG
838 bool found = false;
839 for (size_t i = 0; i < gcLiveArrayBuffers.length(); i++)
840 found |= gcLiveArrayBuffers[i] == &buffer;
841 JS_ASSERT(found);
842 #endif
843 } else if (gcLiveArrayBuffers.append(&buffer)) {
844 buffer.setInLiveList(true);
845 } else {
846 CrashAtUnhandlableOOM("OOM while updating live array buffers");
847 }
848 }
849 }
851 /* static */ void
852 ArrayBufferObject::sweep(JSCompartment *compartment)
853 {
854 JSRuntime *rt = compartment->runtimeFromMainThread();
855 ArrayBufferVector &gcLiveArrayBuffers = compartment->gcLiveArrayBuffers;
857 for (size_t i = 0; i < gcLiveArrayBuffers.length(); i++) {
858 ArrayBufferObject *buffer = gcLiveArrayBuffers[i];
860 JS_ASSERT(buffer->inLiveList());
861 buffer->setInLiveList(false);
863 ArrayBufferViewObject *viewsHead = buffer->viewList();
864 JS_ASSERT(viewsHead);
865 buffer->setViewList(UpdateObjectIfRelocated(rt, &viewsHead));
867 // Rebuild the list of views of the ArrayBufferObject, discarding dead
868 // views. If there is only one view, it will have already been marked.
869 ArrayBufferViewObject *prevLiveView = nullptr;
870 ArrayBufferViewObject *view = viewsHead;
871 while (view) {
872 JS_ASSERT(buffer->compartment() == view->compartment());
873 ArrayBufferViewObject *nextView = view->nextView();
874 if (!IsObjectAboutToBeFinalized(&view)) {
875 view->setNextView(prevLiveView);
876 prevLiveView = view;
877 }
878 view = UpdateObjectIfRelocated(rt, &nextView);
879 }
881 buffer->setViewList(prevLiveView);
882 }
884 gcLiveArrayBuffers.clear();
885 }
887 void
888 ArrayBufferObject::resetArrayBufferList(JSCompartment *comp)
889 {
890 ArrayBufferVector &gcLiveArrayBuffers = comp->gcLiveArrayBuffers;
892 for (size_t i = 0; i < gcLiveArrayBuffers.length(); i++) {
893 ArrayBufferObject *buffer = gcLiveArrayBuffers[i];
895 JS_ASSERT(buffer->inLiveList());
896 buffer->setInLiveList(false);
897 }
899 gcLiveArrayBuffers.clear();
900 }
902 /* static */ bool
903 ArrayBufferObject::saveArrayBufferList(JSCompartment *comp, ArrayBufferVector &vector)
904 {
905 const ArrayBufferVector &gcLiveArrayBuffers = comp->gcLiveArrayBuffers;
907 for (size_t i = 0; i < gcLiveArrayBuffers.length(); i++) {
908 if (!vector.append(gcLiveArrayBuffers[i]))
909 return false;
910 }
912 return true;
913 }
915 /* static */ void
916 ArrayBufferObject::restoreArrayBufferLists(ArrayBufferVector &vector)
917 {
918 for (size_t i = 0; i < vector.length(); i++) {
919 ArrayBufferObject *buffer = vector[i];
921 JS_ASSERT(!buffer->inLiveList());
922 buffer->setInLiveList(true);
924 buffer->compartment()->gcLiveArrayBuffers.infallibleAppend(buffer);
925 }
926 }
928 /*
929 * ArrayBufferViewObject
930 */
932 /*
933 * This method is used to trace TypedArrayObjects and DataViewObjects. We need
934 * a custom tracer because some of an ArrayBufferViewObject's reserved slots
935 * are weak references, and some need to be updated specially during moving
936 * GCs.
937 */
938 /* static */ void
939 ArrayBufferViewObject::trace(JSTracer *trc, JSObject *obj)
940 {
941 HeapSlot &bufSlot = obj->getReservedSlotRef(BUFFER_SLOT);
942 MarkSlot(trc, &bufSlot, "typedarray.buffer");
944 // Update obj's data pointer if the array buffer moved. Note that during
945 // initialization, bufSlot may still contain |undefined|.
946 if (bufSlot.isObject()) {
947 ArrayBufferObject &buf = AsArrayBuffer(&bufSlot.toObject());
948 int32_t offset = obj->getReservedSlot(BYTEOFFSET_SLOT).toInt32();
949 MOZ_ASSERT(buf.dataPointer() != nullptr);
950 obj->initPrivate(buf.dataPointer() + offset);
951 }
953 /* Update NEXT_VIEW_SLOT, if the view moved. */
954 IsSlotMarked(&obj->getReservedSlotRef(NEXT_VIEW_SLOT));
955 }
957 void
958 ArrayBufferViewObject::neuter(void *newData)
959 {
960 MOZ_ASSERT(newData != nullptr);
961 if (is<DataViewObject>())
962 as<DataViewObject>().neuter(newData);
963 else if (is<TypedArrayObject>())
964 as<TypedArrayObject>().neuter(newData);
965 else
966 as<TypedObject>().neuter(newData);
967 }
969 /* static */ ArrayBufferObject *
970 ArrayBufferViewObject::bufferObject(JSContext *cx, Handle<ArrayBufferViewObject *> thisObject)
971 {
972 if (thisObject->is<TypedArrayObject>()) {
973 Rooted<TypedArrayObject *> typedArray(cx, &thisObject->as<TypedArrayObject>());
974 if (!TypedArrayObject::ensureHasBuffer(cx, typedArray))
975 return nullptr;
976 }
977 return &thisObject->getFixedSlot(BUFFER_SLOT).toObject().as<ArrayBufferObject>();
978 }
980 /* JS Friend API */
982 JS_FRIEND_API(bool)
983 JS_IsArrayBufferViewObject(JSObject *obj)
984 {
985 obj = CheckedUnwrap(obj);
986 return obj ? obj->is<ArrayBufferViewObject>() : false;
987 }
989 JS_FRIEND_API(JSObject *)
990 js::UnwrapArrayBufferView(JSObject *obj)
991 {
992 if (JSObject *unwrapped = CheckedUnwrap(obj))
993 return unwrapped->is<ArrayBufferViewObject>() ? unwrapped : nullptr;
994 return nullptr;
995 }
997 JS_FRIEND_API(uint32_t)
998 JS_GetArrayBufferByteLength(JSObject *obj)
999 {
1000 obj = CheckedUnwrap(obj);
1001 return obj ? AsArrayBuffer(obj).byteLength() : 0;
1002 }
1004 JS_FRIEND_API(uint8_t *)
1005 JS_GetArrayBufferData(JSObject *obj)
1006 {
1007 obj = CheckedUnwrap(obj);
1008 if (!obj)
1009 return nullptr;
1010 return AsArrayBuffer(obj).dataPointer();
1011 }
1013 JS_FRIEND_API(uint8_t *)
1014 JS_GetStableArrayBufferData(JSContext *cx, HandleObject objArg)
1015 {
1016 JSObject *obj = CheckedUnwrap(objArg);
1017 if (!obj)
1018 return nullptr;
1020 Rooted<ArrayBufferObject*> buffer(cx, &AsArrayBuffer(obj));
1021 if (!ArrayBufferObject::ensureNonInline(cx, buffer))
1022 return nullptr;
1024 return buffer->dataPointer();
1025 }
1027 JS_FRIEND_API(bool)
1028 JS_NeuterArrayBuffer(JSContext *cx, HandleObject obj,
1029 NeuterDataDisposition changeData)
1030 {
1031 if (!obj->is<ArrayBufferObject>()) {
1032 JS_ReportError(cx, "ArrayBuffer object required");
1033 return false;
1034 }
1036 Rooted<ArrayBufferObject*> buffer(cx, &obj->as<ArrayBufferObject>());
1038 if (!buffer->canNeuter(cx)) {
1039 js_ReportOverRecursed(cx);
1040 return false;
1041 }
1043 void *newData;
1044 if (changeData == ChangeData && buffer->hasStealableContents()) {
1045 newData = AllocateArrayBufferContents(cx, buffer->byteLength());
1046 if (!newData)
1047 return false;
1048 } else {
1049 newData = buffer->dataPointer();
1050 }
1052 ArrayBufferObject::neuter(cx, buffer, newData);
1053 return true;
1054 }
1056 JS_FRIEND_API(JSObject *)
1057 JS_NewArrayBuffer(JSContext *cx, uint32_t nbytes)
1058 {
1059 JS_ASSERT(nbytes <= INT32_MAX);
1060 return ArrayBufferObject::create(cx, nbytes);
1061 }
1063 JS_PUBLIC_API(JSObject *)
1064 JS_NewArrayBufferWithContents(JSContext *cx, size_t nbytes, void *contents)
1065 {
1066 JS_ASSERT(contents);
1067 return ArrayBufferObject::create(cx, nbytes, contents, TenuredObject, false);
1068 }
1070 JS_PUBLIC_API(void *)
1071 JS_AllocateArrayBufferContents(JSContext *maybecx, uint32_t nbytes)
1072 {
1073 return AllocateArrayBufferContents(maybecx, nbytes);
1074 }
1076 JS_PUBLIC_API(void *)
1077 JS_ReallocateArrayBufferContents(JSContext *maybecx, uint32_t nbytes, void *oldContents, uint32_t oldNbytes)
1078 {
1079 return AllocateArrayBufferContents(maybecx, nbytes, oldContents, oldNbytes);
1080 }
1082 JS_FRIEND_API(bool)
1083 JS_IsArrayBufferObject(JSObject *obj)
1084 {
1085 obj = CheckedUnwrap(obj);
1086 return obj ? obj->is<ArrayBufferObject>() : false;
1087 }
1089 JS_FRIEND_API(JSObject *)
1090 js::UnwrapArrayBuffer(JSObject *obj)
1091 {
1092 if (JSObject *unwrapped = CheckedUnwrap(obj))
1093 return unwrapped->is<ArrayBufferObject>() ? unwrapped : nullptr;
1094 return nullptr;
1095 }
1097 JS_PUBLIC_API(void *)
1098 JS_StealArrayBufferContents(JSContext *cx, HandleObject objArg)
1099 {
1100 JSObject *obj = CheckedUnwrap(objArg);
1101 if (!obj)
1102 return nullptr;
1104 if (!obj->is<ArrayBufferObject>()) {
1105 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
1106 return nullptr;
1107 }
1109 Rooted<ArrayBufferObject*> buffer(cx, &obj->as<ArrayBufferObject>());
1110 return ArrayBufferObject::stealContents(cx, buffer);
1111 }
1113 JS_PUBLIC_API(JSObject *)
1114 JS_NewMappedArrayBufferWithContents(JSContext *cx, size_t nbytes, void *contents)
1115 {
1116 JS_ASSERT(contents);
1117 return ArrayBufferObject::create(cx, nbytes, contents, TenuredObject, true);
1118 }
1120 JS_PUBLIC_API(void *)
1121 JS_CreateMappedArrayBufferContents(int fd, size_t offset, size_t length)
1122 {
1123 return ArrayBufferObject::createMappedContents(fd, offset, length);
1124 }
1126 JS_PUBLIC_API(void)
1127 JS_ReleaseMappedArrayBufferContents(void *contents, size_t length)
1128 {
1129 DeallocateMappedContent(contents, length);
1130 }
1132 JS_FRIEND_API(bool)
1133 JS_IsMappedArrayBufferObject(JSObject *obj)
1134 {
1135 obj = CheckedUnwrap(obj);
1136 if (!obj)
1137 return false;
1139 return obj->is<ArrayBufferObject>()
1140 ? obj->as<ArrayBufferObject>().isMappedArrayBuffer()
1141 : false;
1142 }
1144 JS_FRIEND_API(void *)
1145 JS_GetArrayBufferViewData(JSObject *obj)
1146 {
1147 obj = CheckedUnwrap(obj);
1148 if (!obj)
1149 return nullptr;
1150 return obj->is<DataViewObject>() ? obj->as<DataViewObject>().dataPointer()
1151 : obj->as<TypedArrayObject>().viewData();
1152 }
1154 JS_FRIEND_API(JSObject *)
1155 JS_GetArrayBufferViewBuffer(JSContext *cx, JSObject *obj)
1156 {
1157 obj = CheckedUnwrap(obj);
1158 if (!obj)
1159 return nullptr;
1160 Rooted<ArrayBufferViewObject *> viewObject(cx, &obj->as<ArrayBufferViewObject>());
1161 return ArrayBufferViewObject::bufferObject(cx, viewObject);
1162 }
1164 JS_FRIEND_API(uint32_t)
1165 JS_GetArrayBufferViewByteLength(JSObject *obj)
1166 {
1167 obj = CheckedUnwrap(obj);
1168 if (!obj)
1169 return 0;
1170 return obj->is<DataViewObject>()
1171 ? obj->as<DataViewObject>().byteLength()
1172 : obj->as<TypedArrayObject>().byteLength();
1173 }
1175 JS_FRIEND_API(JSObject *)
1176 JS_GetObjectAsArrayBufferView(JSObject *obj, uint32_t *length, uint8_t **data)
1177 {
1178 if (!(obj = CheckedUnwrap(obj)))
1179 return nullptr;
1180 if (!(obj->is<ArrayBufferViewObject>()))
1181 return nullptr;
1183 *length = obj->is<DataViewObject>()
1184 ? obj->as<DataViewObject>().byteLength()
1185 : obj->as<TypedArrayObject>().byteLength();
1187 *data = static_cast<uint8_t*>(obj->is<DataViewObject>()
1188 ? obj->as<DataViewObject>().dataPointer()
1189 : obj->as<TypedArrayObject>().viewData());
1190 return obj;
1191 }
1193 JS_FRIEND_API(void)
1194 js::GetArrayBufferViewLengthAndData(JSObject *obj, uint32_t *length, uint8_t **data)
1195 {
1196 MOZ_ASSERT(obj->is<ArrayBufferViewObject>());
1198 *length = obj->is<DataViewObject>()
1199 ? obj->as<DataViewObject>().byteLength()
1200 : obj->as<TypedArrayObject>().byteLength();
1202 *data = static_cast<uint8_t*>(obj->is<DataViewObject>()
1203 ? obj->as<DataViewObject>().dataPointer()
1204 : obj->as<TypedArrayObject>().viewData());
1205 }
1207 JS_FRIEND_API(JSObject *)
1208 JS_GetObjectAsArrayBuffer(JSObject *obj, uint32_t *length, uint8_t **data)
1209 {
1210 if (!(obj = CheckedUnwrap(obj)))
1211 return nullptr;
1212 if (!IsArrayBuffer(obj))
1213 return nullptr;
1215 *length = AsArrayBuffer(obj).byteLength();
1216 *data = AsArrayBuffer(obj).dataPointer();
1218 return obj;
1219 }
1221 JS_FRIEND_API(void)
1222 js::GetArrayBufferLengthAndData(JSObject *obj, uint32_t *length, uint8_t **data)
1223 {
1224 MOZ_ASSERT(IsArrayBuffer(obj));
1225 *length = AsArrayBuffer(obj).byteLength();
1226 *data = AsArrayBuffer(obj).dataPointer();
1227 }