1/*
2 * Copyright (C) 2013-2019 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#pragma once
27
28#include "ArrayBufferView.h"
29#include "DeferGC.h"
30#include "Error.h"
31#include "ExceptionHelpers.h"
32#include "JSArrayBuffer.h"
33#include "JSGenericTypedArrayView.h"
34#include "TypeError.h"
35#include "TypedArrays.h"
36#include <wtf/text/StringConcatenateNumbers.h>
37
38namespace JSC {
39
40template<typename Adaptor>
41JSGenericTypedArrayView<Adaptor>::JSGenericTypedArrayView(
42 VM& vm, ConstructionContext& context)
43 : Base(vm, context)
44{
45}
46
47template<typename Adaptor>
48JSGenericTypedArrayView<Adaptor>* JSGenericTypedArrayView<Adaptor>::create(
49 ExecState* exec, Structure* structure, unsigned length)
50{
51 VM& vm = exec->vm();
52 auto scope = DECLARE_THROW_SCOPE(vm);
53 ConstructionContext context(vm, structure, length, sizeof(typename Adaptor::Type));
54 if (!context) {
55 throwOutOfMemoryError(exec, scope);
56 return nullptr;
57 }
58 JSGenericTypedArrayView* result =
59 new (NotNull, allocateCell<JSGenericTypedArrayView>(vm.heap))
60 JSGenericTypedArrayView(vm, context);
61 result->finishCreation(vm);
62 return result;
63}
64
65template<typename Adaptor>
66JSGenericTypedArrayView<Adaptor>* JSGenericTypedArrayView<Adaptor>::createWithFastVector(
67 ExecState* exec, Structure* structure, unsigned length, void* vector)
68{
69 VM& vm = exec->vm();
70 ConstructionContext context(structure, length, vector);
71 RELEASE_ASSERT(context);
72 JSGenericTypedArrayView* result =
73 new (NotNull, allocateCell<JSGenericTypedArrayView>(vm.heap))
74 JSGenericTypedArrayView(vm, context);
75 result->finishCreation(vm);
76 return result;
77}
78
79template<typename Adaptor>
80JSGenericTypedArrayView<Adaptor>* JSGenericTypedArrayView<Adaptor>::createUninitialized(ExecState* exec, Structure* structure, unsigned length)
81{
82 VM& vm = exec->vm();
83 auto scope = DECLARE_THROW_SCOPE(vm);
84 ConstructionContext context(
85 vm, structure, length, sizeof(typename Adaptor::Type),
86 ConstructionContext::DontInitialize);
87 if (!context) {
88 throwOutOfMemoryError(exec, scope);
89 return nullptr;
90 }
91 JSGenericTypedArrayView* result =
92 new (NotNull, allocateCell<JSGenericTypedArrayView>(vm.heap))
93 JSGenericTypedArrayView(vm, context);
94 result->finishCreation(vm);
95 return result;
96}
97
98template<typename Adaptor>
99JSGenericTypedArrayView<Adaptor>* JSGenericTypedArrayView<Adaptor>::create(
100 ExecState* exec, Structure* structure, RefPtr<ArrayBuffer>&& buffer,
101 unsigned byteOffset, unsigned length)
102{
103 VM& vm = exec->vm();
104 auto scope = DECLARE_THROW_SCOPE(vm);
105 size_t size = sizeof(typename Adaptor::Type);
106 ASSERT(buffer);
107 if (!ArrayBufferView::verifySubRangeLength(*buffer, byteOffset, length, size)) {
108 throwException(exec, scope, createRangeError(exec, "Length out of range of buffer"));
109 return nullptr;
110 }
111 if (!ArrayBufferView::verifyByteOffsetAlignment(byteOffset, size)) {
112 throwException(exec, scope, createRangeError(exec, "Byte offset is not aligned"));
113 return nullptr;
114 }
115 ConstructionContext context(vm, structure, WTFMove(buffer), byteOffset, length);
116 ASSERT(context);
117 JSGenericTypedArrayView* result =
118 new (NotNull, allocateCell<JSGenericTypedArrayView>(vm.heap))
119 JSGenericTypedArrayView(vm, context);
120 result->finishCreation(vm);
121 return result;
122}
123
124template<typename Adaptor>
125JSGenericTypedArrayView<Adaptor>* JSGenericTypedArrayView<Adaptor>::create(
126 VM& vm, Structure* structure, RefPtr<typename Adaptor::ViewType>&& impl)
127{
128 ConstructionContext context(vm, structure, impl->possiblySharedBuffer(), impl->byteOffset(), impl->length());
129 ASSERT(context);
130 JSGenericTypedArrayView* result =
131 new (NotNull, allocateCell<JSGenericTypedArrayView>(vm.heap))
132 JSGenericTypedArrayView(vm, context);
133 result->finishCreation(vm);
134 return result;
135}
136
137template<typename Adaptor>
138JSGenericTypedArrayView<Adaptor>* JSGenericTypedArrayView<Adaptor>::create(
139 Structure* structure, JSGlobalObject* globalObject,
140 RefPtr<typename Adaptor::ViewType>&& impl)
141{
142 return create(globalObject->vm(), structure, WTFMove(impl));
143}
144
145template<typename Adaptor>
146bool JSGenericTypedArrayView<Adaptor>::validateRange(
147 ExecState* exec, unsigned offset, unsigned length)
148{
149 VM& vm = exec->vm();
150 auto scope = DECLARE_THROW_SCOPE(vm);
151 if (canAccessRangeQuickly(offset, length))
152 return true;
153
154 throwException(exec, scope, createRangeError(exec, "Range consisting of offset and length are out of bounds"));
155 return false;
156}
157
158template<typename Adaptor>
159template<typename OtherAdaptor>
160bool JSGenericTypedArrayView<Adaptor>::setWithSpecificType(
161 ExecState* exec, unsigned offset, JSGenericTypedArrayView<OtherAdaptor>* other,
162 unsigned otherOffset, unsigned length, CopyType type)
163{
164 // Handle the hilarious case: the act of getting the length could have resulted
165 // in neutering. Well, no. That'll never happen because there cannot be
166 // side-effects on getting the length of a typed array. But predicting where there
167 // are, or aren't, side-effects is a fool's game so we resort to this cheap
168 // check. Worst case, if we're wrong, people start seeing less things get copied
169 // but we won't have a security vulnerability.
170 length = std::min(length, other->length());
171
172 RELEASE_ASSERT(other->canAccessRangeQuickly(otherOffset, length));
173 if (!validateRange(exec, offset, length))
174 return false;
175
176 // This method doesn't support copying between the same array. Note that
177 // set() will only call this if the types differ, which implicitly guarantees
178 // that we can't be the same array. This is relevant because the way we detect
179 // non-overlapping is by checking if either (a) either array doesn't have a
180 // backing buffer or (b) the backing buffers are different, but that doesn't
181 // catch the case where it's the *same* array - fortunately though, this code
182 // path never needs to worry about that case.
183 ASSERT(static_cast<JSCell*>(this) != static_cast<JSCell*>(other));
184
185 // 1) If the two arrays are non-overlapping, we can copy in any order we like
186 // and we don't need an intermediate buffer. Arrays are definitely
187 // non-overlapping if either one of them has no backing buffer (that means
188 // that it *owns* its philosophical backing buffer) or if they have
189 // different backing buffers.
190 // 2) If the two arrays overlap but have the same element size, we can do a
191 // memmove-like copy where we flip-flop direction based on which vector
192 // starts before the other:
193 // A) If the destination vector is before the source vector, then a forward
194 // copy is in order.
195 // B) If the destination vector is after the source vector, then a backward
196 // copy is in order.
197 // 3) If we have different element sizes and there is a chance of overlap then
198 // we need an intermediate vector.
199
200 // NB. Comparisons involving elementSize will be constant-folded by template
201 // specialization.
202
203 unsigned otherElementSize = sizeof(typename OtherAdaptor::Type);
204
205 // Handle cases (1) and (2A).
206 if (!hasArrayBuffer() || !other->hasArrayBuffer()
207 || existingBuffer() != other->existingBuffer()
208 || (elementSize == otherElementSize && vector() <= other->vector())
209 || type == CopyType::LeftToRight) {
210 for (unsigned i = 0; i < length; ++i) {
211 setIndexQuicklyToNativeValue(
212 offset + i, OtherAdaptor::template convertTo<Adaptor>(
213 other->getIndexQuicklyAsNativeValue(i + otherOffset)));
214 }
215 return true;
216 }
217
218 // Now we either have (2B) or (3) - so first we try to cover (2B).
219 if (elementSize == otherElementSize) {
220 for (unsigned i = length; i--;) {
221 setIndexQuicklyToNativeValue(
222 offset + i, OtherAdaptor::template convertTo<Adaptor>(
223 other->getIndexQuicklyAsNativeValue(i + otherOffset)));
224 }
225 return true;
226 }
227
228 // Fail: we need an intermediate transfer buffer (i.e. case (3)).
229 Vector<typename Adaptor::Type, 32> transferBuffer(length);
230 for (unsigned i = length; i--;) {
231 transferBuffer[i] = OtherAdaptor::template convertTo<Adaptor>(
232 other->getIndexQuicklyAsNativeValue(i + otherOffset));
233 }
234 for (unsigned i = length; i--;)
235 setIndexQuicklyToNativeValue(offset + i, transferBuffer[i]);
236
237 return true;
238}
239
240template<typename Adaptor>
241bool JSGenericTypedArrayView<Adaptor>::set(
242 ExecState* exec, unsigned offset, JSObject* object, unsigned objectOffset, unsigned length, CopyType type)
243{
244 VM& vm = exec->vm();
245 auto scope = DECLARE_THROW_SCOPE(vm);
246
247 const ClassInfo* ci = object->classInfo(vm);
248 if (ci->typedArrayStorageType == Adaptor::typeValue) {
249 // The super fast case: we can just memcpy since we're the same type.
250 JSGenericTypedArrayView* other = jsCast<JSGenericTypedArrayView*>(object);
251 length = std::min(length, other->length());
252
253 RELEASE_ASSERT(other->canAccessRangeQuickly(objectOffset, length));
254 bool success = validateRange(exec, offset, length);
255 EXCEPTION_ASSERT(!scope.exception() == success);
256 if (!success)
257 return false;
258
259 memmove(typedVector() + offset, other->typedVector() + objectOffset, length * elementSize);
260 return true;
261 }
262
263 switch (ci->typedArrayStorageType) {
264 case TypeInt8:
265 RELEASE_AND_RETURN(scope, setWithSpecificType<Int8Adaptor>(
266 exec, offset, jsCast<JSInt8Array*>(object), objectOffset, length, type));
267 case TypeInt16:
268 RELEASE_AND_RETURN(scope, setWithSpecificType<Int16Adaptor>(
269 exec, offset, jsCast<JSInt16Array*>(object), objectOffset, length, type));
270 case TypeInt32:
271 RELEASE_AND_RETURN(scope, setWithSpecificType<Int32Adaptor>(
272 exec, offset, jsCast<JSInt32Array*>(object), objectOffset, length, type));
273 case TypeUint8:
274 RELEASE_AND_RETURN(scope, setWithSpecificType<Uint8Adaptor>(
275 exec, offset, jsCast<JSUint8Array*>(object), objectOffset, length, type));
276 case TypeUint8Clamped:
277 RELEASE_AND_RETURN(scope, setWithSpecificType<Uint8ClampedAdaptor>(
278 exec, offset, jsCast<JSUint8ClampedArray*>(object), objectOffset, length, type));
279 case TypeUint16:
280 RELEASE_AND_RETURN(scope, setWithSpecificType<Uint16Adaptor>(
281 exec, offset, jsCast<JSUint16Array*>(object), objectOffset, length, type));
282 case TypeUint32:
283 RELEASE_AND_RETURN(scope, setWithSpecificType<Uint32Adaptor>(
284 exec, offset, jsCast<JSUint32Array*>(object), objectOffset, length, type));
285 case TypeFloat32:
286 RELEASE_AND_RETURN(scope, setWithSpecificType<Float32Adaptor>(
287 exec, offset, jsCast<JSFloat32Array*>(object), objectOffset, length, type));
288 case TypeFloat64:
289 RELEASE_AND_RETURN(scope, setWithSpecificType<Float64Adaptor>(
290 exec, offset, jsCast<JSFloat64Array*>(object), objectOffset, length, type));
291 case NotTypedArray:
292 case TypeDataView: {
293 bool success = validateRange(exec, offset, length);
294 EXCEPTION_ASSERT(!scope.exception() == success);
295 if (!success)
296 return false;
297
298 // We could optimize this case. But right now, we don't.
299 for (unsigned i = 0; i < length; ++i) {
300 JSValue value = object->get(exec, i + objectOffset);
301 RETURN_IF_EXCEPTION(scope, false);
302 bool success = setIndex(exec, offset + i, value);
303 EXCEPTION_ASSERT(!scope.exception() || !success);
304 if (!success)
305 return false;
306 }
307 return true;
308 } }
309
310 RELEASE_ASSERT_NOT_REACHED();
311 return false;
312}
313
314template<typename Adaptor>
315RefPtr<typename Adaptor::ViewType> JSGenericTypedArrayView<Adaptor>::possiblySharedTypedImpl()
316{
317 return Adaptor::ViewType::tryCreate(possiblySharedBuffer(), byteOffset(), length());
318}
319
320template<typename Adaptor>
321RefPtr<typename Adaptor::ViewType> JSGenericTypedArrayView<Adaptor>::unsharedTypedImpl()
322{
323 return Adaptor::ViewType::tryCreate(unsharedBuffer(), byteOffset(), length());
324}
325
326template<typename Adaptor>
327ArrayBuffer* JSGenericTypedArrayView<Adaptor>::existingBuffer()
328{
329 return existingBufferInButterfly();
330}
331
332template<typename Adaptor>
333EncodedJSValue JSGenericTypedArrayView<Adaptor>::throwNeuteredTypedArrayTypeError(ExecState* exec, EncodedJSValue object, PropertyName)
334{
335 VM& vm = exec->vm();
336 auto scope = DECLARE_THROW_SCOPE(vm);
337 ASSERT_UNUSED(object, jsCast<JSGenericTypedArrayView*>(JSValue::decode(object))->isNeutered());
338 return throwVMTypeError(exec, scope, typedArrayBufferHasBeenDetachedErrorMessage);
339}
340
341template<typename Adaptor>
342bool JSGenericTypedArrayView<Adaptor>::getOwnPropertySlot(
343 JSObject* object, ExecState* exec, PropertyName propertyName, PropertySlot& slot)
344{
345 JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(object);
346
347 if (Optional<uint32_t> index = parseIndex(propertyName)) {
348 if (thisObject->isNeutered()) {
349 slot.setCustom(thisObject, static_cast<unsigned>(PropertyAttribute::None), throwNeuteredTypedArrayTypeError);
350 return true;
351 }
352
353 if (thisObject->canGetIndexQuickly(index.value())) {
354 slot.setValue(thisObject, static_cast<unsigned>(PropertyAttribute::DontDelete), thisObject->getIndexQuickly(index.value()));
355 return true;
356 }
357
358 return false;
359 }
360
361 if (isCanonicalNumericIndexString(propertyName)) {
362 if (thisObject->isNeutered()) {
363 slot.setCustom(thisObject, static_cast<unsigned>(PropertyAttribute::None), throwNeuteredTypedArrayTypeError);
364 return true;
365 }
366
367 return false;
368 }
369
370 return Base::getOwnPropertySlot(thisObject, exec, propertyName, slot);
371}
372
373template<typename Adaptor>
374bool JSGenericTypedArrayView<Adaptor>::put(
375 JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value,
376 PutPropertySlot& slot)
377{
378 VM& vm = exec->vm();
379 auto scope = DECLARE_THROW_SCOPE(vm);
380
381 JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(cell);
382
383 // https://tc39.github.io/ecma262/#sec-integer-indexed-exotic-objects-set-p-v-receiver
384 // Ignore the receiver even if the receiver is altered to non base value.
385 // 9.4.5.5-2-b-i Return ? IntegerIndexedElementSet(O, numericIndex, V).
386 if (Optional<uint32_t> index = parseIndex(propertyName))
387 RELEASE_AND_RETURN(scope, putByIndex(thisObject, exec, index.value(), value, slot.isStrictMode()));
388
389 if (isCanonicalNumericIndexString(propertyName)) {
390 if (thisObject->isNeutered())
391 throwTypeError(exec, scope, typedArrayBufferHasBeenDetachedErrorMessage);
392 return false;
393 }
394
395 RELEASE_AND_RETURN(scope, Base::put(thisObject, exec, propertyName, value, slot));
396}
397
398template<typename Adaptor>
399bool JSGenericTypedArrayView<Adaptor>::defineOwnProperty(
400 JSObject* object, ExecState* exec, PropertyName propertyName,
401 const PropertyDescriptor& descriptor, bool shouldThrow)
402{
403 VM& vm = exec->vm();
404 auto scope = DECLARE_THROW_SCOPE(vm);
405 JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(object);
406
407 if (Optional<uint32_t> index = parseIndex(propertyName)) {
408 auto throwTypeErrorIfNeeded = [&] (const char* errorMessage) -> bool {
409 if (shouldThrow)
410 throwTypeError(exec, scope, makeString(errorMessage, *index));
411 return false;
412 };
413
414 if (index.value() >= thisObject->m_length)
415 return false;
416
417 if (descriptor.isAccessorDescriptor())
418 return throwTypeErrorIfNeeded("Attempting to store accessor property on a typed array at index: ");
419
420 if (descriptor.configurable())
421 return throwTypeErrorIfNeeded("Attempting to configure non-configurable property on a typed array at index: ");
422
423 if (!descriptor.enumerable() || !descriptor.writable())
424 return throwTypeErrorIfNeeded("Attempting to store non-enumerable or non-writable property on a typed array at index: ");
425
426 if (descriptor.value())
427 RELEASE_AND_RETURN(scope, thisObject->putByIndex(thisObject, exec, index.value(), descriptor.value(), shouldThrow));
428
429 return true;
430 }
431
432 if (isCanonicalNumericIndexString(propertyName))
433 return false;
434
435 RELEASE_AND_RETURN(scope, Base::defineOwnProperty(thisObject, exec, propertyName, descriptor, shouldThrow));
436}
437
438template<typename Adaptor>
439bool JSGenericTypedArrayView<Adaptor>::deleteProperty(
440 JSCell* cell, ExecState* exec, PropertyName propertyName)
441{
442 VM& vm = exec->vm();
443 auto scope = DECLARE_THROW_SCOPE(vm);
444 JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(cell);
445
446 if (thisObject->isNeutered())
447 return typeError(exec, scope, true, typedArrayBufferHasBeenDetachedErrorMessage);
448
449 if (parseIndex(propertyName))
450 return false;
451
452 return Base::deleteProperty(thisObject, exec, propertyName);
453}
454
455template<typename Adaptor>
456bool JSGenericTypedArrayView<Adaptor>::getOwnPropertySlotByIndex(
457 JSObject* object, ExecState*, unsigned propertyName, PropertySlot& slot)
458{
459 JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(object);
460
461 if (thisObject->isNeutered()) {
462 slot.setCustom(thisObject, static_cast<unsigned>(PropertyAttribute::None), throwNeuteredTypedArrayTypeError);
463 return true;
464 }
465
466 if (!thisObject->canGetIndexQuickly(propertyName))
467 return false;
468
469 slot.setValue(thisObject, static_cast<unsigned>(PropertyAttribute::DontDelete), thisObject->getIndexQuickly(propertyName));
470 return true;
471}
472
473template<typename Adaptor>
474bool JSGenericTypedArrayView<Adaptor>::putByIndex(
475 JSCell* cell, ExecState* exec, unsigned propertyName, JSValue value, bool)
476{
477 JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(cell);
478 return thisObject->setIndex(exec, propertyName, value);
479}
480
481template<typename Adaptor>
482bool JSGenericTypedArrayView<Adaptor>::deletePropertyByIndex(
483 JSCell* cell, ExecState* exec, unsigned propertyName)
484{
485 return cell->methodTable(exec->vm())->deleteProperty(cell, exec, Identifier::from(exec, propertyName));
486}
487
488template<typename Adaptor>
489void JSGenericTypedArrayView<Adaptor>::getOwnPropertyNames(
490 JSObject* object, ExecState* exec, PropertyNameArray& array, EnumerationMode mode)
491{
492 JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(object);
493
494 if (array.includeStringProperties()) {
495 for (unsigned i = 0; i < thisObject->m_length; ++i)
496 array.add(Identifier::from(exec, i));
497 }
498
499 return Base::getOwnPropertyNames(object, exec, array, mode);
500}
501
502template<typename Adaptor>
503size_t JSGenericTypedArrayView<Adaptor>::estimatedSize(JSCell* cell, VM& vm)
504{
505 JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(cell);
506
507 if (thisObject->m_mode == OversizeTypedArray)
508 return Base::estimatedSize(thisObject, vm) + thisObject->byteSize();
509 if (thisObject->m_mode == FastTypedArray && thisObject->hasVector())
510 return Base::estimatedSize(thisObject, vm) + thisObject->byteSize();
511
512 return Base::estimatedSize(thisObject, vm);
513}
514
515template<typename Adaptor>
516void JSGenericTypedArrayView<Adaptor>::visitChildren(JSCell* cell, SlotVisitor& visitor)
517{
518 JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(cell);
519 Base::visitChildren(thisObject, visitor);
520
521 TypedArrayMode mode;
522 void* vector;
523 size_t byteSize;
524
525 {
526 auto locker = holdLock(thisObject->cellLock());
527 mode = thisObject->m_mode;
528 vector = thisObject->vector();
529 byteSize = thisObject->byteSize();
530 }
531
532 switch (mode) {
533 case FastTypedArray: {
534 if (vector)
535 visitor.markAuxiliary(vector);
536 break;
537 }
538
539 case OversizeTypedArray: {
540 visitor.reportExtraMemoryVisited(byteSize);
541 break;
542 }
543
544 case WastefulTypedArray:
545 break;
546
547 case DataViewMode:
548 RELEASE_ASSERT_NOT_REACHED();
549 break;
550 }
551}
552
553} // namespace JSC
554