/* * Copyright (C) 2015 Dominic Szablewski (dominic@phoboslab.org) * Copyright (C) 2016 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "JSTypedArray.h" #include "APICast.h" #include "APIUtils.h" #include "ClassInfo.h" #include "JSCInlines.h" #include "JSGenericTypedArrayViewInlines.h" #include "JSTypedArrays.h" #include "TypedArrayController.h" #include using namespace JSC; // Helper functions. inline JSTypedArrayType toJSTypedArrayType(TypedArrayType type) { switch (type) { case JSC::TypeDataView: case NotTypedArray: return kJSTypedArrayTypeNone; case TypeInt8: return kJSTypedArrayTypeInt8Array; case TypeUint8: return kJSTypedArrayTypeUint8Array; case TypeUint8Clamped: return kJSTypedArrayTypeUint8ClampedArray; case TypeInt16: return kJSTypedArrayTypeInt16Array; case TypeUint16: return kJSTypedArrayTypeUint16Array; case TypeInt32: return kJSTypedArrayTypeInt32Array; case TypeUint32: return kJSTypedArrayTypeUint32Array; case TypeFloat32: return kJSTypedArrayTypeFloat32Array; case TypeFloat64: return kJSTypedArrayTypeFloat64Array; case TypeBigInt64: return kJSTypedArrayTypeBigInt64Array; case TypeBigUint64: return kJSTypedArrayTypeBigUint64Array; } RELEASE_ASSERT_NOT_REACHED(); } inline TypedArrayType toTypedArrayType(JSTypedArrayType type) { switch (type) { case kJSTypedArrayTypeArrayBuffer: case kJSTypedArrayTypeNone: return NotTypedArray; case kJSTypedArrayTypeInt8Array: return TypeInt8; case kJSTypedArrayTypeUint8Array: return TypeUint8; case kJSTypedArrayTypeUint8ClampedArray: return TypeUint8Clamped; case kJSTypedArrayTypeInt16Array: return TypeInt16; case kJSTypedArrayTypeUint16Array: return TypeUint16; case kJSTypedArrayTypeInt32Array: return TypeInt32; case kJSTypedArrayTypeUint32Array: return TypeUint32; case kJSTypedArrayTypeFloat32Array: return TypeFloat32; case kJSTypedArrayTypeFloat64Array: return TypeFloat64; case kJSTypedArrayTypeBigInt64Array: return TypeBigInt64; case kJSTypedArrayTypeBigUint64Array: return TypeBigUint64; } RELEASE_ASSERT_NOT_REACHED(); } static JSObject* createTypedArray(JSGlobalObject* globalObject, JSTypedArrayType type, RefPtr&& buffer, size_t offset, size_t length) { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); if (!buffer) { throwOutOfMemoryError(globalObject, scope); return nullptr; } switch (type) { case kJSTypedArrayTypeInt8Array: return JSInt8Array::create(globalObject, globalObject->typedArrayStructure(TypeInt8), WTFMove(buffer), offset, length); case kJSTypedArrayTypeInt16Array: return JSInt16Array::create(globalObject, globalObject->typedArrayStructure(TypeInt16), WTFMove(buffer), offset, length); case kJSTypedArrayTypeInt32Array: return JSInt32Array::create(globalObject, globalObject->typedArrayStructure(TypeInt32), WTFMove(buffer), offset, length); case kJSTypedArrayTypeUint8Array: return JSUint8Array::create(globalObject, globalObject->typedArrayStructure(TypeUint8), WTFMove(buffer), offset, length); case kJSTypedArrayTypeUint8ClampedArray: return JSUint8ClampedArray::create(globalObject, globalObject->typedArrayStructure(TypeUint8Clamped), WTFMove(buffer), offset, length); case kJSTypedArrayTypeUint16Array: return JSUint16Array::create(globalObject, globalObject->typedArrayStructure(TypeUint16), WTFMove(buffer), offset, length); case kJSTypedArrayTypeUint32Array: return JSUint32Array::create(globalObject, globalObject->typedArrayStructure(TypeUint32), WTFMove(buffer), offset, length); case kJSTypedArrayTypeFloat32Array: return JSFloat32Array::create(globalObject, globalObject->typedArrayStructure(TypeFloat32), WTFMove(buffer), offset, length); case kJSTypedArrayTypeFloat64Array: return JSFloat64Array::create(globalObject, globalObject->typedArrayStructure(TypeFloat64), WTFMove(buffer), offset, length); case kJSTypedArrayTypeBigInt64Array: return JSBigInt64Array::create(globalObject, globalObject->typedArrayStructure(TypeBigInt64), WTFMove(buffer), offset, length); case kJSTypedArrayTypeBigUint64Array: return JSBigUint64Array::create(globalObject, globalObject->typedArrayStructure(TypeBigUint64), WTFMove(buffer), offset, length); case kJSTypedArrayTypeArrayBuffer: case kJSTypedArrayTypeNone: RELEASE_ASSERT_NOT_REACHED(); } return nullptr; } // Implementations of the API functions. JSTypedArrayType JSValueGetTypedArrayType(JSContextRef ctx, JSValueRef valueRef, JSValueRef*) { JSGlobalObject* globalObject = toJS(ctx); VM& vm = globalObject->vm(); JSLockHolder locker(vm); JSValue value = toJS(globalObject, valueRef); if (!value.isObject()) return kJSTypedArrayTypeNone; JSObject* object = value.getObject(); if (jsDynamicCast(vm, object)) return kJSTypedArrayTypeArrayBuffer; return toJSTypedArrayType(object->classInfo(vm)->typedArrayStorageType); } JSObjectRef JSObjectMakeTypedArray(JSContextRef ctx, JSTypedArrayType arrayType, size_t length, JSValueRef* exception) { JSGlobalObject* globalObject = toJS(ctx); VM& vm = globalObject->vm(); JSLockHolder locker(vm); auto scope = DECLARE_CATCH_SCOPE(vm); if (arrayType == kJSTypedArrayTypeNone || arrayType == kJSTypedArrayTypeArrayBuffer) return nullptr; unsigned elementByteSize = elementSize(toTypedArrayType(arrayType)); auto buffer = ArrayBuffer::tryCreate(length, elementByteSize); JSObject* result = createTypedArray(globalObject, arrayType, WTFMove(buffer), 0, length); if (handleExceptionIfNeeded(scope, ctx, exception) == ExceptionStatus::DidThrow) return nullptr; return toRef(result); } JSObjectRef JSObjectMakeTypedArrayWithBytesNoCopy(JSContextRef ctx, JSTypedArrayType arrayType, void* bytes, size_t length, JSTypedArrayBytesDeallocator destructor, void* destructorContext, JSValueRef* exception) { JSGlobalObject* globalObject = toJS(ctx); VM& vm = globalObject->vm(); JSLockHolder locker(vm); auto scope = DECLARE_CATCH_SCOPE(vm); if (arrayType == kJSTypedArrayTypeNone || arrayType == kJSTypedArrayTypeArrayBuffer) return nullptr; unsigned elementByteSize = elementSize(toTypedArrayType(arrayType)); auto buffer = ArrayBuffer::createFromBytes(bytes, length, createSharedTask([=](void* p) { if (destructor) destructor(p, destructorContext); })); JSObject* result = createTypedArray(globalObject, arrayType, WTFMove(buffer), 0, length / elementByteSize); if (handleExceptionIfNeeded(scope, ctx, exception) == ExceptionStatus::DidThrow) return nullptr; return toRef(result); } JSObjectRef JSObjectMakeTypedArrayWithArrayBuffer(JSContextRef ctx, JSTypedArrayType arrayType, JSObjectRef jsBufferRef, JSValueRef* exception) { JSGlobalObject* globalObject = toJS(ctx); VM& vm = globalObject->vm(); JSLockHolder locker(vm); auto scope = DECLARE_CATCH_SCOPE(vm); if (arrayType == kJSTypedArrayTypeNone || arrayType == kJSTypedArrayTypeArrayBuffer) return nullptr; JSArrayBuffer* jsBuffer = jsDynamicCast(vm, toJS(jsBufferRef)); if (!jsBuffer) { setException(ctx, exception, createTypeError(globalObject, "JSObjectMakeTypedArrayWithArrayBuffer expects buffer to be an Array Buffer object")); return nullptr; } RefPtr buffer = jsBuffer->impl(); unsigned elementByteSize = elementSize(toTypedArrayType(arrayType)); JSObject* result = createTypedArray(globalObject, arrayType, WTFMove(buffer), 0, buffer->byteLength() / elementByteSize); if (handleExceptionIfNeeded(scope, ctx, exception) == ExceptionStatus::DidThrow) return nullptr; return toRef(result); } JSObjectRef JSObjectMakeTypedArrayWithArrayBufferAndOffset(JSContextRef ctx, JSTypedArrayType arrayType, JSObjectRef jsBufferRef, size_t offset, size_t length, JSValueRef* exception) { JSGlobalObject* globalObject = toJS(ctx); VM& vm = globalObject->vm(); JSLockHolder locker(vm); auto scope = DECLARE_CATCH_SCOPE(vm); if (arrayType == kJSTypedArrayTypeNone || arrayType == kJSTypedArrayTypeArrayBuffer) return nullptr; JSArrayBuffer* jsBuffer = jsDynamicCast(vm, toJS(jsBufferRef)); if (!jsBuffer) { setException(ctx, exception, createTypeError(globalObject, "JSObjectMakeTypedArrayWithArrayBuffer expects buffer to be an Array Buffer object")); return nullptr; } JSObject* result = createTypedArray(globalObject, arrayType, jsBuffer->impl(), offset, length); if (handleExceptionIfNeeded(scope, ctx, exception) == ExceptionStatus::DidThrow) return nullptr; return toRef(result); } void* JSObjectGetTypedArrayBytesPtr(JSContextRef ctx, JSObjectRef objectRef, JSValueRef* exception) { JSGlobalObject* globalObject = toJS(ctx); VM& vm = globalObject->vm(); JSLockHolder locker(vm); JSObject* object = toJS(objectRef); if (JSArrayBufferView* typedArray = jsDynamicCast(vm, object)) { if (ArrayBuffer* buffer = typedArray->possiblySharedBuffer()) { buffer->pinAndLock(); return buffer->data(); } setException(ctx, exception, createOutOfMemoryError(globalObject)); } return nullptr; } size_t JSObjectGetTypedArrayLength(JSContextRef ctx, JSObjectRef objectRef, JSValueRef*) { JSGlobalObject* globalObject = toJS(ctx); VM& vm = globalObject->vm(); JSObject* object = toJS(objectRef); if (JSArrayBufferView* typedArray = jsDynamicCast(vm, object)) return typedArray->length(); return 0; } size_t JSObjectGetTypedArrayByteLength(JSContextRef ctx, JSObjectRef objectRef, JSValueRef*) { JSGlobalObject* globalObject = toJS(ctx); VM& vm = globalObject->vm(); JSObject* object = toJS(objectRef); if (JSArrayBufferView* typedArray = jsDynamicCast(vm, object)) return typedArray->length() * elementSize(typedArray->classInfo(vm)->typedArrayStorageType); return 0; } size_t JSObjectGetTypedArrayByteOffset(JSContextRef ctx, JSObjectRef objectRef, JSValueRef*) { JSGlobalObject* globalObject = toJS(ctx); VM& vm = globalObject->vm(); JSObject* object = toJS(objectRef); if (JSArrayBufferView* typedArray = jsDynamicCast(vm, object)) return typedArray->byteOffset(); return 0; } JSObjectRef JSObjectGetTypedArrayBuffer(JSContextRef ctx, JSObjectRef objectRef, JSValueRef* exception) { JSGlobalObject* globalObject = toJS(ctx); VM& vm = globalObject->vm(); JSLockHolder locker(vm); JSObject* object = toJS(objectRef); if (JSArrayBufferView* typedArray = jsDynamicCast(vm, object)) { if (ArrayBuffer* buffer = typedArray->possiblySharedBuffer()) return toRef(vm.m_typedArrayController->toJS(globalObject, typedArray->globalObject(vm), buffer)); setException(ctx, exception, createOutOfMemoryError(globalObject)); } return nullptr; } JSObjectRef JSObjectMakeArrayBufferWithBytesNoCopy(JSContextRef ctx, void* bytes, size_t byteLength, JSTypedArrayBytesDeallocator bytesDeallocator, void* deallocatorContext, JSValueRef* exception) { JSGlobalObject* globalObject = toJS(ctx); VM& vm = globalObject->vm(); JSLockHolder locker(vm); auto scope = DECLARE_CATCH_SCOPE(vm); auto buffer = ArrayBuffer::createFromBytes(bytes, byteLength, createSharedTask([=](void* p) { if (bytesDeallocator) bytesDeallocator(p, deallocatorContext); })); JSArrayBuffer* jsBuffer = JSArrayBuffer::create(vm, globalObject->arrayBufferStructure(ArrayBufferSharingMode::Default), WTFMove(buffer)); if (handleExceptionIfNeeded(scope, ctx, exception) == ExceptionStatus::DidThrow) return nullptr; return toRef(jsBuffer); } void* JSObjectGetArrayBufferBytesPtr(JSContextRef ctx, JSObjectRef objectRef, JSValueRef* exception) { JSGlobalObject* globalObject = toJS(ctx); VM& vm = globalObject->vm(); JSLockHolder locker(vm); JSObject* object = toJS(objectRef); if (JSArrayBuffer* jsBuffer = jsDynamicCast(vm, object)) { ArrayBuffer* buffer = jsBuffer->impl(); if (buffer->isWasmMemory()) { setException(ctx, exception, createTypeError(globalObject, "Cannot get the backing buffer for a WebAssembly.Memory"_s)); return nullptr; } buffer->pinAndLock(); return buffer->data(); } return nullptr; } size_t JSObjectGetArrayBufferByteLength(JSContextRef ctx, JSObjectRef objectRef, JSValueRef*) { JSGlobalObject* globalObject = toJS(ctx); VM& vm = globalObject->vm(); JSObject* object = toJS(objectRef); if (JSArrayBuffer* jsBuffer = jsDynamicCast(vm, object)) return jsBuffer->impl()->byteLength(); return 0; }