1/*
2 * Copyright (C) 2013 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#ifndef JSGenericTypedArrayViewInlines_h
27#define JSGenericTypedArrayViewInlines_h
28
29#include "ArrayBufferView.h"
30#include "DeferGC.h"
31#include "Error.h"
32#include "ExceptionHelpers.h"
33#include "JSGenericTypedArrayView.h"
34#include "Reject.h"
35#include "TypedArrays.h"
36
37namespace JSC {
38
39template<typename Adaptor>
40JSGenericTypedArrayView<Adaptor>::JSGenericTypedArrayView(
41    VM& vm, ConstructionContext& context)
42    : Base(vm, context)
43{
44}
45
46template<typename Adaptor>
47JSGenericTypedArrayView<Adaptor>* JSGenericTypedArrayView<Adaptor>::create(
48    ExecState* exec, Structure* structure, unsigned length)
49{
50    ConstructionContext context(exec->vm(), structure, length, sizeof(typename Adaptor::Type));
51    if (!context) {
52        exec->vm().throwException(exec, createOutOfMemoryError(structure->globalObject()));
53        return 0;
54    }
55    JSGenericTypedArrayView* result =
56        new (NotNull, allocateCell<JSGenericTypedArrayView>(exec->vm().heap))
57        JSGenericTypedArrayView(exec->vm(), context);
58    result->finishCreation(exec->vm());
59    return result;
60}
61
62template<typename Adaptor>
63JSGenericTypedArrayView<Adaptor>* JSGenericTypedArrayView<Adaptor>::createUninitialized(
64    ExecState* exec, Structure* structure, unsigned length)
65{
66    ConstructionContext context(
67        exec->vm(), structure, length, sizeof(typename Adaptor::Type),
68        ConstructionContext::DontInitialize);
69    if (!context) {
70        exec->vm().throwException(exec, createOutOfMemoryError(structure->globalObject()));
71        return 0;
72    }
73    JSGenericTypedArrayView* result =
74        new (NotNull, allocateCell<JSGenericTypedArrayView>(exec->vm().heap))
75        JSGenericTypedArrayView(exec->vm(), context);
76    result->finishCreation(exec->vm());
77    return result;
78}
79
80template<typename Adaptor>
81JSGenericTypedArrayView<Adaptor>* JSGenericTypedArrayView<Adaptor>::create(
82    ExecState* exec, Structure* structure, PassRefPtr<ArrayBuffer> passedBuffer,
83    unsigned byteOffset, unsigned length)
84{
85    RefPtr<ArrayBuffer> buffer = passedBuffer;
86    if (!ArrayBufferView::verifySubRange<typename Adaptor::Type>(buffer, byteOffset, length)) {
87        exec->vm().throwException(
88            exec, createRangeError(exec, "Byte offset and length out of range of buffer"));
89        return 0;
90    }
91    ConstructionContext context(exec->vm(), structure, buffer, byteOffset, length);
92    ASSERT(context);
93    JSGenericTypedArrayView* result =
94        new (NotNull, allocateCell<JSGenericTypedArrayView>(exec->vm().heap))
95        JSGenericTypedArrayView(exec->vm(), context);
96    result->finishCreation(exec->vm());
97    return result;
98}
99
100template<typename Adaptor>
101JSGenericTypedArrayView<Adaptor>* JSGenericTypedArrayView<Adaptor>::create(
102    VM& vm, Structure* structure, PassRefPtr<typename Adaptor::ViewType> impl)
103{
104    RefPtr<ArrayBuffer> buffer = impl->buffer();
105    ConstructionContext context(vm, structure, buffer, impl->byteOffset(), impl->length());
106    ASSERT(context);
107    JSGenericTypedArrayView* result =
108        new (NotNull, allocateCell<JSGenericTypedArrayView>(vm.heap))
109        JSGenericTypedArrayView(vm, context);
110    result->finishCreation(vm);
111    return result;
112}
113
114template<typename Adaptor>
115JSGenericTypedArrayView<Adaptor>* JSGenericTypedArrayView<Adaptor>::create(
116    Structure* structure, JSGlobalObject* globalObject,
117    PassRefPtr<typename Adaptor::ViewType> impl)
118{
119    return create(globalObject->vm(), structure, impl);
120}
121
122template<typename Adaptor>
123bool JSGenericTypedArrayView<Adaptor>::validateRange(
124    ExecState* exec, unsigned offset, unsigned length)
125{
126    if (canAccessRangeQuickly(offset, length))
127        return true;
128
129    exec->vm().throwException(exec, createRangeError(exec, "Range consisting of offset and length are out of bounds"));
130    return false;
131}
132
133template<typename Adaptor>
134template<typename OtherAdaptor>
135bool JSGenericTypedArrayView<Adaptor>::setWithSpecificType(
136    ExecState* exec, JSGenericTypedArrayView<OtherAdaptor>* other,
137    unsigned offset, unsigned length)
138{
139    // Handle the hilarious case: the act of getting the length could have resulted
140    // in neutering. Well, no. That'll never happen because there cannot be
141    // side-effects on getting the length of a typed array. But predicting where there
142    // are, or aren't, side-effects is a fool's game so we resort to this cheap
143    // check. Worst case, if we're wrong, people start seeing less things get copied
144    // but we won't have a security vulnerability.
145    length = std::min(length, other->length());
146
147    if (!validateRange(exec, offset, length))
148        return false;
149
150    if (other->length() != length) {
151        exec->vm().throwException(exec, createRangeError(exec, "Length of incoming array changed unexpectedly."));
152        return false;
153    }
154
155    // This method doesn't support copying between the same array. Note that
156    // set() will only call this if the types differ, which implicitly guarantees
157    // that we can't be the same array. This is relevant because the way we detect
158    // non-overlapping is by checking if either (a) either array doesn't have a
159    // backing buffer or (b) the backing buffers are different, but that doesn't
160    // catch the case where it's the *same* array - fortunately though, this code
161    // path never needs to worry about that case.
162    ASSERT(static_cast<JSCell*>(this) != static_cast<JSCell*>(other));
163
164    // 1) If the two arrays are non-overlapping, we can copy in any order we like
165    //    and we don't need an intermediate buffer. Arrays are definitely
166    //    non-overlapping if either one of them has no backing buffer (that means
167    //    that it *owns* its philosophical backing buffer) or if they have
168    //    different backing buffers.
169    // 2) If the two arrays overlap but have the same element size, we can do a
170    //    memmove-like copy where we flip-flop direction based on which vector
171    //    starts before the other:
172    //    A) If the destination vector is before the source vector, then a forward
173    //       copy is in order.
174    //    B) If the destination vector is after the source vector, then a backward
175    //       copy is in order.
176    // 3) If we have different element sizes and there is a chance of overlap then
177    //    we need an intermediate vector.
178
179    // NB. Comparisons involving elementSize will be constant-folded by template
180    // specialization.
181
182    unsigned otherElementSize = sizeof(typename OtherAdaptor::Type);
183
184    // Handle cases (1) and (2B).
185    if (!hasArrayBuffer() || !other->hasArrayBuffer()
186        || existingBuffer() != other->existingBuffer()
187        || (elementSize == otherElementSize && vector() > other->vector())) {
188        for (unsigned i = length; i--;) {
189            setIndexQuicklyToNativeValue(
190                offset + i, OtherAdaptor::template convertTo<Adaptor>(
191                    other->getIndexQuicklyAsNativeValue(i)));
192        }
193        return true;
194    }
195
196    // Now we either have (2A) or (3) - so first we try to cover (2A).
197    if (elementSize == otherElementSize) {
198        for (unsigned i = 0; i < length; ++i) {
199            setIndexQuicklyToNativeValue(
200                offset + i, OtherAdaptor::template convertTo<Adaptor>(
201                    other->getIndexQuicklyAsNativeValue(i)));
202        }
203        return true;
204    }
205
206    // Fail: we need an intermediate transfer buffer (i.e. case (3)).
207    Vector<typename Adaptor::Type, 32> transferBuffer(length);
208    for (unsigned i = length; i--;) {
209        transferBuffer[i] = OtherAdaptor::template convertTo<Adaptor>(
210            other->getIndexQuicklyAsNativeValue(i));
211    }
212    for (unsigned i = length; i--;)
213        setIndexQuicklyToNativeValue(offset + i, transferBuffer[i]);
214
215    return true;
216}
217
218template<typename Adaptor>
219bool JSGenericTypedArrayView<Adaptor>::set(
220    ExecState* exec, JSObject* object, unsigned offset, unsigned length)
221{
222    const ClassInfo* ci = object->classInfo();
223    if (ci->typedArrayStorageType == Adaptor::typeValue) {
224        // The super fast case: we can just memcpy since we're the same type.
225        JSGenericTypedArrayView* other = jsCast<JSGenericTypedArrayView*>(object);
226        length = std::min(length, other->length());
227
228        if (!validateRange(exec, offset, length))
229            return false;
230
231        memmove(typedVector() + offset, other->typedVector(), other->byteLength());
232        return true;
233    }
234
235    switch (ci->typedArrayStorageType) {
236    case TypeInt8:
237        return setWithSpecificType<Int8Adaptor>(
238            exec, jsCast<JSInt8Array*>(object), offset, length);
239    case TypeInt16:
240        return setWithSpecificType<Int16Adaptor>(
241            exec, jsCast<JSInt16Array*>(object), offset, length);
242    case TypeInt32:
243        return setWithSpecificType<Int32Adaptor>(
244            exec, jsCast<JSInt32Array*>(object), offset, length);
245    case TypeUint8:
246        return setWithSpecificType<Uint8Adaptor>(
247            exec, jsCast<JSUint8Array*>(object), offset, length);
248    case TypeUint8Clamped:
249        return setWithSpecificType<Uint8ClampedAdaptor>(
250            exec, jsCast<JSUint8ClampedArray*>(object), offset, length);
251    case TypeUint16:
252        return setWithSpecificType<Uint16Adaptor>(
253            exec, jsCast<JSUint16Array*>(object), offset, length);
254    case TypeUint32:
255        return setWithSpecificType<Uint32Adaptor>(
256            exec, jsCast<JSUint32Array*>(object), offset, length);
257    case TypeFloat32:
258        return setWithSpecificType<Float32Adaptor>(
259            exec, jsCast<JSFloat32Array*>(object), offset, length);
260    case TypeFloat64:
261        return setWithSpecificType<Float64Adaptor>(
262            exec, jsCast<JSFloat64Array*>(object), offset, length);
263    case NotTypedArray:
264    case TypeDataView: {
265        if (!validateRange(exec, offset, length))
266            return false;
267        // We could optimize this case. But right now, we don't.
268        for (unsigned i = 0; i < length; ++i) {
269            JSValue value = object->get(exec, i);
270            if (!setIndex(exec, offset + i, value))
271                return false;
272        }
273        return true;
274    } }
275
276    RELEASE_ASSERT_NOT_REACHED();
277    return false;
278}
279
280template<typename Adaptor>
281ArrayBuffer* JSGenericTypedArrayView<Adaptor>::existingBuffer()
282{
283    return existingBufferInButterfly();
284}
285
286template<typename Adaptor>
287bool JSGenericTypedArrayView<Adaptor>::getOwnPropertySlot(
288    JSObject* object, ExecState* exec, PropertyName propertyName, PropertySlot& slot)
289{
290    JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(object);
291    if (propertyName == exec->propertyNames().length) {
292        slot.setValue(thisObject, DontDelete | ReadOnly, jsNumber(thisObject->length()));
293        return true;
294    }
295
296    if (propertyName == exec->propertyNames().byteLength) {
297        slot.setValue(thisObject, DontDelete | ReadOnly, jsNumber(thisObject->byteLength()));
298        return true;
299    }
300
301    unsigned index = propertyName.asIndex();
302    if (index != PropertyName::NotAnIndex && thisObject->canGetIndexQuickly(index)) {
303        slot.setValue(thisObject, DontDelete | ReadOnly, thisObject->getIndexQuickly(index));
304        return true;
305    }
306
307    return Base::getOwnPropertySlot(thisObject, exec, propertyName, slot);
308}
309
310template<typename Adaptor>
311void JSGenericTypedArrayView<Adaptor>::put(
312    JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value,
313    PutPropertySlot& slot)
314{
315    JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(cell);
316
317    if (propertyName == exec->propertyNames().length) {
318        // Firefox appears to simply ignore attempts to store to the length property.
319        // Even in strict mode. I will do the same.
320        return;
321    }
322
323    unsigned index = propertyName.asIndex();
324    if (index != PropertyName::NotAnIndex) {
325        putByIndex(thisObject, exec, index, value, slot.isStrictMode());
326        return;
327    }
328
329    Base::put(thisObject, exec, propertyName, value, slot);
330}
331
332template<typename Adaptor>
333bool JSGenericTypedArrayView<Adaptor>::defineOwnProperty(
334    JSObject* object, ExecState* exec, PropertyName propertyName,
335    const PropertyDescriptor& descriptor, bool shouldThrow)
336{
337    JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(object);
338
339    // This is matching Firefox behavior. In particular, it rejects all attempts to
340    // defineOwnProperty for indexed properties on typed arrays, even if they're out
341    // of bounds.
342    if (propertyName == exec->propertyNames().length
343        || propertyName.asIndex() != PropertyName::NotAnIndex)
344        return reject(exec, shouldThrow, "Attempting to write to a read-only typed array property.");
345
346    return Base::defineOwnProperty(thisObject, exec, propertyName, descriptor, shouldThrow);
347}
348
349template<typename Adaptor>
350bool JSGenericTypedArrayView<Adaptor>::deleteProperty(
351    JSCell* cell, ExecState* exec, PropertyName propertyName)
352{
353    JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(cell);
354
355    if (propertyName == exec->propertyNames().length
356        || propertyName.asIndex() != PropertyName::NotAnIndex)
357        return false;
358
359    return Base::deleteProperty(thisObject, exec, propertyName);
360}
361
362template<typename Adaptor>
363bool JSGenericTypedArrayView<Adaptor>::getOwnPropertySlotByIndex(
364    JSObject* object, ExecState* exec, unsigned propertyName, PropertySlot& slot)
365{
366    JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(object);
367
368    if (propertyName > MAX_ARRAY_INDEX) {
369        return thisObject->methodTable()->getOwnPropertySlot(
370            thisObject, exec, Identifier::from(exec, propertyName), slot);
371    }
372
373    if (!thisObject->canGetIndexQuickly(propertyName))
374        return false;
375
376    slot.setValue(thisObject, None, thisObject->getIndexQuickly(propertyName));
377    return true;
378}
379
380template<typename Adaptor>
381void JSGenericTypedArrayView<Adaptor>::putByIndex(
382    JSCell* cell, ExecState* exec, unsigned propertyName, JSValue value, bool shouldThrow)
383{
384    JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(cell);
385
386    if (propertyName > MAX_ARRAY_INDEX) {
387        PutPropertySlot slot(JSValue(thisObject), shouldThrow);
388        thisObject->methodTable()->put(
389            thisObject, exec, Identifier::from(exec, propertyName), value, slot);
390        return;
391    }
392
393    thisObject->setIndex(exec, propertyName, value);
394}
395
396template<typename Adaptor>
397bool JSGenericTypedArrayView<Adaptor>::deletePropertyByIndex(
398    JSCell* cell, ExecState* exec, unsigned propertyName)
399{
400    if (propertyName > MAX_ARRAY_INDEX) {
401        return cell->methodTable()->deleteProperty(
402            cell, exec, Identifier::from(exec, propertyName));
403    }
404
405    return false;
406}
407
408template<typename Adaptor>
409void JSGenericTypedArrayView<Adaptor>::getOwnNonIndexPropertyNames(
410    JSObject* object, ExecState* exec, PropertyNameArray& array, EnumerationMode mode)
411{
412    JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(object);
413
414    if (mode == IncludeDontEnumProperties)
415        array.add(exec->propertyNames().length);
416
417    Base::getOwnNonIndexPropertyNames(thisObject, exec, array, mode);
418}
419
420template<typename Adaptor>
421void JSGenericTypedArrayView<Adaptor>::getOwnPropertyNames(
422    JSObject* object, ExecState* exec, PropertyNameArray& array, EnumerationMode mode)
423{
424    JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(object);
425
426    for (unsigned i = 0; i < thisObject->m_length; ++i)
427        array.add(Identifier::from(exec, i));
428
429    return Base::getOwnPropertyNames(object, exec, array, mode);
430}
431
432template<typename Adaptor>
433void JSGenericTypedArrayView<Adaptor>::visitChildren(JSCell* cell, SlotVisitor& visitor)
434{
435    JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(cell);
436
437    switch (thisObject->m_mode) {
438    case FastTypedArray: {
439        if (thisObject->m_vector)
440            visitor.copyLater(thisObject, TypedArrayVectorCopyToken, thisObject->m_vector, thisObject->byteSize());
441        break;
442    }
443
444    case OversizeTypedArray: {
445        visitor.reportExtraMemoryUsage(thisObject, thisObject->byteSize());
446        break;
447    }
448
449    case WastefulTypedArray:
450        break;
451
452    case DataViewMode:
453        RELEASE_ASSERT_NOT_REACHED();
454        break;
455    }
456
457    Base::visitChildren(thisObject, visitor);
458}
459
460template<typename Adaptor>
461void JSGenericTypedArrayView<Adaptor>::copyBackingStore(
462    JSCell* cell, CopyVisitor& visitor, CopyToken token)
463{
464    JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(cell);
465
466    if (token == TypedArrayVectorCopyToken
467        && visitor.checkIfShouldCopy(thisObject->m_vector)) {
468        ASSERT(thisObject->m_vector);
469        void* oldVector = thisObject->m_vector;
470        void* newVector = visitor.allocateNewSpace(thisObject->byteSize());
471        memcpy(newVector, oldVector, thisObject->byteSize());
472        thisObject->m_vector = newVector;
473        visitor.didCopy(oldVector, thisObject->byteSize());
474    }
475
476    Base::copyBackingStore(thisObject, visitor, token);
477}
478
479template<typename Adaptor>
480ArrayBuffer* JSGenericTypedArrayView<Adaptor>::slowDownAndWasteMemory(JSArrayBufferView* object)
481{
482    JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(object);
483
484    // We play this game because we want this to be callable even from places that
485    // don't have access to ExecState* or the VM, and we only allocate so little
486    // memory here that it's not necessary to trigger a GC - just accounting what
487    // we have done is good enough. The sort of bizarro exception to the "allocating
488    // little memory" is when we transfer a backing buffer into the C heap; this
489    // will temporarily get counted towards heap footprint (incorrectly, in the case
490    // of adopting an oversize typed array) but we don't GC here anyway. That's
491    // almost certainly fine. The worst case is if you created a ton of fast typed
492    // arrays, and did nothing but caused all of them to slow down and waste memory.
493    // In that case, your memory footprint will double before the GC realizes what's
494    // up. But if you do *anything* to trigger a GC watermark check, it will know
495    // that you *had* done those allocations and it will GC appropriately.
496    Heap* heap = Heap::heap(thisObject);
497    DeferGCForAWhile deferGC(*heap);
498
499    ASSERT(!thisObject->hasIndexingHeader());
500
501    size_t size = thisObject->byteSize();
502
503    if (thisObject->m_mode == FastTypedArray
504        && !thisObject->butterfly() && size >= sizeof(IndexingHeader)) {
505        ASSERT(thisObject->m_vector);
506        // Reuse already allocated memory if at all possible.
507        thisObject->m_butterfly.setWithoutWriteBarrier(
508            static_cast<IndexingHeader*>(thisObject->m_vector)->butterfly());
509    } else {
510        VM& vm = *heap->vm();
511        thisObject->m_butterfly.set(vm, thisObject, Butterfly::createOrGrowArrayRight(
512            thisObject->butterfly(), vm, thisObject, thisObject->structure(),
513            thisObject->structure()->outOfLineCapacity(), false, 0, 0));
514    }
515
516    RefPtr<ArrayBuffer> buffer;
517
518    switch (thisObject->m_mode) {
519    case FastTypedArray:
520        buffer = ArrayBuffer::create(thisObject->m_vector, thisObject->byteLength());
521        break;
522
523    case OversizeTypedArray:
524        // FIXME: consider doing something like "subtracting" from extra memory
525        // cost, since right now this case will cause the GC to think that we reallocated
526        // the whole buffer.
527        buffer = ArrayBuffer::createAdopted(thisObject->m_vector, thisObject->byteLength());
528        break;
529
530    default:
531        RELEASE_ASSERT_NOT_REACHED();
532        break;
533    }
534
535    thisObject->butterfly()->indexingHeader()->setArrayBuffer(buffer.get());
536    thisObject->m_vector = buffer->data();
537    thisObject->m_mode = WastefulTypedArray;
538    heap->addReference(thisObject, buffer.get());
539
540    return buffer.get();
541}
542
543template<typename Adaptor>
544PassRefPtr<ArrayBufferView>
545JSGenericTypedArrayView<Adaptor>::getTypedArrayImpl(JSArrayBufferView* object)
546{
547    JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(object);
548    return thisObject->typedImpl();
549}
550
551} // namespace JSC
552
553#endif // JSGenericTypedArrayViewInlines_h
554
555