1/*
2 * Copyright (C) 2015 Dominic Szablewski (dominic@phoboslab.org)
3 * Copyright (C) 2016 Apple Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "JSTypedArray.h"
29
30#include "APICast.h"
31#include "APIUtils.h"
32#include "ClassInfo.h"
33#include "Error.h"
34#include "JSArrayBufferViewInlines.h"
35#include "JSCInlines.h"
36#include "JSDataView.h"
37#include "JSGenericTypedArrayViewInlines.h"
38#include "JSTypedArrays.h"
39#include "TypedArrayController.h"
40#include <wtf/RefPtr.h>
41
42using namespace JSC;
43
44// Helper functions.
45
46inline JSTypedArrayType toJSTypedArrayType(TypedArrayType type)
47{
48 switch (type) {
49 case JSC::TypeDataView:
50 case NotTypedArray:
51 return kJSTypedArrayTypeNone;
52 case TypeInt8:
53 return kJSTypedArrayTypeInt8Array;
54 case TypeUint8:
55 return kJSTypedArrayTypeUint8Array;
56 case TypeUint8Clamped:
57 return kJSTypedArrayTypeUint8ClampedArray;
58 case TypeInt16:
59 return kJSTypedArrayTypeInt16Array;
60 case TypeUint16:
61 return kJSTypedArrayTypeUint16Array;
62 case TypeInt32:
63 return kJSTypedArrayTypeInt32Array;
64 case TypeUint32:
65 return kJSTypedArrayTypeUint32Array;
66 case TypeFloat32:
67 return kJSTypedArrayTypeFloat32Array;
68 case TypeFloat64:
69 return kJSTypedArrayTypeFloat64Array;
70 }
71 RELEASE_ASSERT_NOT_REACHED();
72}
73
74inline TypedArrayType toTypedArrayType(JSTypedArrayType type)
75{
76 switch (type) {
77 case kJSTypedArrayTypeArrayBuffer:
78 case kJSTypedArrayTypeNone:
79 return NotTypedArray;
80 case kJSTypedArrayTypeInt8Array:
81 return TypeInt8;
82 case kJSTypedArrayTypeUint8Array:
83 return TypeUint8;
84 case kJSTypedArrayTypeUint8ClampedArray:
85 return TypeUint8Clamped;
86 case kJSTypedArrayTypeInt16Array:
87 return TypeInt16;
88 case kJSTypedArrayTypeUint16Array:
89 return TypeUint16;
90 case kJSTypedArrayTypeInt32Array:
91 return TypeInt32;
92 case kJSTypedArrayTypeUint32Array:
93 return TypeUint32;
94 case kJSTypedArrayTypeFloat32Array:
95 return TypeFloat32;
96 case kJSTypedArrayTypeFloat64Array:
97 return TypeFloat64;
98 }
99 RELEASE_ASSERT_NOT_REACHED();
100}
101
102static JSObject* createTypedArray(ExecState* exec, JSTypedArrayType type, RefPtr<ArrayBuffer>&& buffer, size_t offset, size_t length)
103{
104 VM& vm = exec->vm();
105 auto scope = DECLARE_THROW_SCOPE(vm);
106 JSGlobalObject* globalObject = exec->lexicalGlobalObject();
107 if (!buffer) {
108 throwOutOfMemoryError(exec, scope);
109 return nullptr;
110 }
111 switch (type) {
112 case kJSTypedArrayTypeInt8Array:
113 return JSInt8Array::create(exec, globalObject->typedArrayStructure(TypeInt8), WTFMove(buffer), offset, length);
114 case kJSTypedArrayTypeInt16Array:
115 return JSInt16Array::create(exec, globalObject->typedArrayStructure(TypeInt16), WTFMove(buffer), offset, length);
116 case kJSTypedArrayTypeInt32Array:
117 return JSInt32Array::create(exec, globalObject->typedArrayStructure(TypeInt32), WTFMove(buffer), offset, length);
118 case kJSTypedArrayTypeUint8Array:
119 return JSUint8Array::create(exec, globalObject->typedArrayStructure(TypeUint8), WTFMove(buffer), offset, length);
120 case kJSTypedArrayTypeUint8ClampedArray:
121 return JSUint8ClampedArray::create(exec, globalObject->typedArrayStructure(TypeUint8Clamped), WTFMove(buffer), offset, length);
122 case kJSTypedArrayTypeUint16Array:
123 return JSUint16Array::create(exec, globalObject->typedArrayStructure(TypeUint16), WTFMove(buffer), offset, length);
124 case kJSTypedArrayTypeUint32Array:
125 return JSUint32Array::create(exec, globalObject->typedArrayStructure(TypeUint32), WTFMove(buffer), offset, length);
126 case kJSTypedArrayTypeFloat32Array:
127 return JSFloat32Array::create(exec, globalObject->typedArrayStructure(TypeFloat32), WTFMove(buffer), offset, length);
128 case kJSTypedArrayTypeFloat64Array:
129 return JSFloat64Array::create(exec, globalObject->typedArrayStructure(TypeFloat64), WTFMove(buffer), offset, length);
130 case kJSTypedArrayTypeArrayBuffer:
131 case kJSTypedArrayTypeNone:
132 RELEASE_ASSERT_NOT_REACHED();
133 }
134 return nullptr;
135}
136
137// Implementations of the API functions.
138
139JSTypedArrayType JSValueGetTypedArrayType(JSContextRef ctx, JSValueRef valueRef, JSValueRef*)
140{
141
142 ExecState* exec = toJS(ctx);
143 VM& vm = exec->vm();
144 JSLockHolder locker(vm);
145
146 JSValue value = toJS(exec, valueRef);
147 if (!value.isObject())
148 return kJSTypedArrayTypeNone;
149 JSObject* object = value.getObject();
150
151 if (jsDynamicCast<JSArrayBuffer*>(vm, object))
152 return kJSTypedArrayTypeArrayBuffer;
153
154 return toJSTypedArrayType(object->classInfo(vm)->typedArrayStorageType);
155}
156
157JSObjectRef JSObjectMakeTypedArray(JSContextRef ctx, JSTypedArrayType arrayType, size_t length, JSValueRef* exception)
158{
159 ExecState* exec = toJS(ctx);
160 VM& vm = exec->vm();
161 JSLockHolder locker(vm);
162 auto scope = DECLARE_CATCH_SCOPE(vm);
163
164 if (arrayType == kJSTypedArrayTypeNone || arrayType == kJSTypedArrayTypeArrayBuffer)
165 return nullptr;
166
167 unsigned elementByteSize = elementSize(toTypedArrayType(arrayType));
168
169 auto buffer = ArrayBuffer::tryCreate(length, elementByteSize);
170 JSObject* result = createTypedArray(exec, arrayType, WTFMove(buffer), 0, length);
171 if (handleExceptionIfNeeded(scope, exec, exception) == ExceptionStatus::DidThrow)
172 return nullptr;
173 return toRef(result);
174}
175
176JSObjectRef JSObjectMakeTypedArrayWithBytesNoCopy(JSContextRef ctx, JSTypedArrayType arrayType, void* bytes, size_t length, JSTypedArrayBytesDeallocator destructor, void* destructorContext, JSValueRef* exception)
177{
178 ExecState* exec = toJS(ctx);
179 VM& vm = exec->vm();
180 JSLockHolder locker(vm);
181 auto scope = DECLARE_CATCH_SCOPE(vm);
182
183 if (arrayType == kJSTypedArrayTypeNone || arrayType == kJSTypedArrayTypeArrayBuffer)
184 return nullptr;
185
186 unsigned elementByteSize = elementSize(toTypedArrayType(arrayType));
187
188 auto buffer = ArrayBuffer::createFromBytes(bytes, length, [=](void* p) {
189 if (destructor)
190 destructor(p, destructorContext);
191 });
192 JSObject* result = createTypedArray(exec, arrayType, WTFMove(buffer), 0, length / elementByteSize);
193 if (handleExceptionIfNeeded(scope, exec, exception) == ExceptionStatus::DidThrow)
194 return nullptr;
195 return toRef(result);
196}
197
198JSObjectRef JSObjectMakeTypedArrayWithArrayBuffer(JSContextRef ctx, JSTypedArrayType arrayType, JSObjectRef jsBufferRef, JSValueRef* exception)
199{
200 ExecState* exec = toJS(ctx);
201 VM& vm = exec->vm();
202 JSLockHolder locker(vm);
203 auto scope = DECLARE_CATCH_SCOPE(vm);
204
205 if (arrayType == kJSTypedArrayTypeNone || arrayType == kJSTypedArrayTypeArrayBuffer)
206 return nullptr;
207
208 JSArrayBuffer* jsBuffer = jsDynamicCast<JSArrayBuffer*>(vm, toJS(jsBufferRef));
209 if (!jsBuffer) {
210 setException(exec, exception, createTypeError(exec, "JSObjectMakeTypedArrayWithArrayBuffer expects buffer to be an Array Buffer object"));
211 return nullptr;
212 }
213
214 RefPtr<ArrayBuffer> buffer = jsBuffer->impl();
215 unsigned elementByteSize = elementSize(toTypedArrayType(arrayType));
216
217 JSObject* result = createTypedArray(exec, arrayType, WTFMove(buffer), 0, buffer->byteLength() / elementByteSize);
218 if (handleExceptionIfNeeded(scope, exec, exception) == ExceptionStatus::DidThrow)
219 return nullptr;
220 return toRef(result);
221}
222
223JSObjectRef JSObjectMakeTypedArrayWithArrayBufferAndOffset(JSContextRef ctx, JSTypedArrayType arrayType, JSObjectRef jsBufferRef, size_t offset, size_t length, JSValueRef* exception)
224{
225 ExecState* exec = toJS(ctx);
226 VM& vm = exec->vm();
227 JSLockHolder locker(vm);
228 auto scope = DECLARE_CATCH_SCOPE(vm);
229
230 if (arrayType == kJSTypedArrayTypeNone || arrayType == kJSTypedArrayTypeArrayBuffer)
231 return nullptr;
232
233 JSArrayBuffer* jsBuffer = jsDynamicCast<JSArrayBuffer*>(vm, toJS(jsBufferRef));
234 if (!jsBuffer) {
235 setException(exec, exception, createTypeError(exec, "JSObjectMakeTypedArrayWithArrayBuffer expects buffer to be an Array Buffer object"));
236 return nullptr;
237 }
238
239 JSObject* result = createTypedArray(exec, arrayType, jsBuffer->impl(), offset, length);
240 if (handleExceptionIfNeeded(scope, exec, exception) == ExceptionStatus::DidThrow)
241 return nullptr;
242 return toRef(result);
243}
244
245void* JSObjectGetTypedArrayBytesPtr(JSContextRef ctx, JSObjectRef objectRef, JSValueRef*)
246{
247 ExecState* exec = toJS(ctx);
248 VM& vm = exec->vm();
249 JSLockHolder locker(vm);
250 JSObject* object = toJS(objectRef);
251
252 if (JSArrayBufferView* typedArray = jsDynamicCast<JSArrayBufferView*>(vm, object)) {
253 ArrayBuffer* buffer = typedArray->possiblySharedBuffer();
254 buffer->pinAndLock();
255 return buffer->data();
256 }
257 return nullptr;
258}
259
260size_t JSObjectGetTypedArrayLength(JSContextRef ctx, JSObjectRef objectRef, JSValueRef*)
261{
262 ExecState* exec = toJS(ctx);
263 VM& vm = exec->vm();
264 JSObject* object = toJS(objectRef);
265
266 if (JSArrayBufferView* typedArray = jsDynamicCast<JSArrayBufferView*>(vm, object))
267 return typedArray->length();
268
269 return 0;
270}
271
272size_t JSObjectGetTypedArrayByteLength(JSContextRef ctx, JSObjectRef objectRef, JSValueRef*)
273{
274 ExecState* exec = toJS(ctx);
275 VM& vm = exec->vm();
276 JSObject* object = toJS(objectRef);
277
278 if (JSArrayBufferView* typedArray = jsDynamicCast<JSArrayBufferView*>(vm, object))
279 return typedArray->length() * elementSize(typedArray->classInfo(vm)->typedArrayStorageType);
280
281 return 0;
282}
283
284size_t JSObjectGetTypedArrayByteOffset(JSContextRef ctx, JSObjectRef objectRef, JSValueRef*)
285{
286 ExecState* exec = toJS(ctx);
287 VM& vm = exec->vm();
288 JSObject* object = toJS(objectRef);
289
290 if (JSArrayBufferView* typedArray = jsDynamicCast<JSArrayBufferView*>(vm, object))
291 return typedArray->byteOffset();
292
293 return 0;
294}
295
296JSObjectRef JSObjectGetTypedArrayBuffer(JSContextRef ctx, JSObjectRef objectRef, JSValueRef*)
297{
298 ExecState* exec = toJS(ctx);
299 VM& vm = exec->vm();
300 JSLockHolder locker(vm);
301 JSObject* object = toJS(objectRef);
302
303 if (JSArrayBufferView* typedArray = jsDynamicCast<JSArrayBufferView*>(vm, object))
304 return toRef(vm.m_typedArrayController->toJS(exec, typedArray->globalObject(vm), typedArray->possiblySharedBuffer()));
305
306 return nullptr;
307}
308
309JSObjectRef JSObjectMakeArrayBufferWithBytesNoCopy(JSContextRef ctx, void* bytes, size_t byteLength, JSTypedArrayBytesDeallocator bytesDeallocator, void* deallocatorContext, JSValueRef* exception)
310{
311 ExecState* exec = toJS(ctx);
312 VM& vm = exec->vm();
313 JSLockHolder locker(vm);
314 auto scope = DECLARE_CATCH_SCOPE(vm);
315
316 auto buffer = ArrayBuffer::createFromBytes(bytes, byteLength, [=](void* p) {
317 if (bytesDeallocator)
318 bytesDeallocator(p, deallocatorContext);
319 });
320
321 JSArrayBuffer* jsBuffer = JSArrayBuffer::create(vm, exec->lexicalGlobalObject()->arrayBufferStructure(ArrayBufferSharingMode::Default), WTFMove(buffer));
322 if (handleExceptionIfNeeded(scope, exec, exception) == ExceptionStatus::DidThrow)
323 return nullptr;
324
325 return toRef(jsBuffer);
326}
327
328void* JSObjectGetArrayBufferBytesPtr(JSContextRef ctx, JSObjectRef objectRef, JSValueRef* exception)
329{
330 ExecState* exec = toJS(ctx);
331 VM& vm = exec->vm();
332 JSLockHolder locker(vm);
333 JSObject* object = toJS(objectRef);
334
335 if (JSArrayBuffer* jsBuffer = jsDynamicCast<JSArrayBuffer*>(vm, object)) {
336 ArrayBuffer* buffer = jsBuffer->impl();
337 if (buffer->isWasmMemory()) {
338 setException(exec, exception, createTypeError(exec, "Cannot get the backing buffer for a WebAssembly.Memory"_s));
339 return nullptr;
340 }
341
342 buffer->pinAndLock();
343 return buffer->data();
344 }
345 return nullptr;
346}
347
348size_t JSObjectGetArrayBufferByteLength(JSContextRef ctx, JSObjectRef objectRef, JSValueRef*)
349{
350 ExecState* exec = toJS(ctx);
351 VM& vm = exec->vm();
352 JSObject* object = toJS(objectRef);
353
354 if (JSArrayBuffer* jsBuffer = jsDynamicCast<JSArrayBuffer*>(vm, object))
355 return jsBuffer->impl()->byteLength();
356
357 return 0;
358}
359