JSGenericTypedArrayViewPrototypeFunctions.h [plain text]
#pragma once
#include "Error.h"
#include "JSArrayBufferViewInlines.h"
#include "JSCBuiltins.h"
#include "JSCJSValueInlines.h"
#include "JSFunction.h"
#include "JSGenericTypedArrayViewInlines.h"
#include "JSGenericTypedArrayViewPrototypeInlines.h"
#include "JSStringJoiner.h"
#include "StructureInlines.h"
#include "TypedArrayAdaptors.h"
#include "TypedArrayController.h"
#include <wtf/StdLibExtras.h>
namespace JSC {
template<typename Functor>
inline JSArrayBufferView* speciesConstruct(ExecState* exec, JSObject* exemplar, MarkedArgumentBuffer& args, const Functor& defaultConstructor)
{
VM& vm = exec->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue constructor = exemplar->get(exec, vm.propertyNames->constructor);
RETURN_IF_EXCEPTION(scope, nullptr);
if (constructor.isUndefined()) {
scope.release();
return defaultConstructor();
}
if (!constructor.isObject()) {
throwTypeError(exec, scope, "constructor Property should not be null"_s);
return nullptr;
}
JSValue species = constructor.get(exec, vm.propertyNames->speciesSymbol);
RETURN_IF_EXCEPTION(scope, nullptr);
if (species.isUndefinedOrNull()) {
scope.release();
return defaultConstructor();
}
JSValue result = construct(exec, species, args, "species is not a constructor");
RETURN_IF_EXCEPTION(scope, nullptr);
if (JSArrayBufferView* view = jsDynamicCast<JSArrayBufferView*>(vm, result)) {
if (!view->isNeutered())
return view;
throwTypeError(exec, scope, typedArrayBufferHasBeenDetachedErrorMessage);
return nullptr;
}
throwTypeError(exec, scope, "species constructor did not return a TypedArray View"_s);
return nullptr;
}
inline unsigned argumentClampedIndexFromStartOrEnd(ExecState* exec, int argument, unsigned length, unsigned undefinedValue = 0)
{
JSValue value = exec->argument(argument);
if (value.isUndefined())
return undefinedValue;
double indexDouble = value.toInteger(exec);
if (indexDouble < 0) {
indexDouble += length;
return indexDouble < 0 ? 0 : static_cast<unsigned>(indexDouble);
}
return indexDouble > length ? length : static_cast<unsigned>(indexDouble);
}
template<typename ViewClass>
EncodedJSValue JSC_HOST_CALL genericTypedArrayViewProtoFuncSet(VM& vm, ExecState* exec)
{
auto scope = DECLARE_THROW_SCOPE(vm);
ViewClass* thisObject = jsCast<ViewClass*>(exec->thisValue());
if (UNLIKELY(!exec->argumentCount()))
return throwVMTypeError(exec, scope, "Expected at least one argument"_s);
unsigned offset;
if (exec->argumentCount() >= 2) {
double offsetNumber = exec->uncheckedArgument(1).toInteger(exec);
RETURN_IF_EXCEPTION(scope, encodedJSValue());
if (UNLIKELY(offsetNumber < 0))
return throwVMRangeError(exec, scope, "Offset should not be negative");
offset = static_cast<unsigned>(std::min(offsetNumber, static_cast<double>(std::numeric_limits<unsigned>::max())));
} else
offset = 0;
if (UNLIKELY(thisObject->isNeutered()))
return throwVMTypeError(exec, scope, typedArrayBufferHasBeenDetachedErrorMessage);
JSObject* sourceArray = jsDynamicCast<JSObject*>(vm, exec->uncheckedArgument(0));
if (UNLIKELY(!sourceArray))
return throwVMTypeError(exec, scope, "First argument should be an object"_s);
unsigned length;
if (isTypedView(sourceArray->classInfo(vm)->typedArrayStorageType)) {
JSArrayBufferView* sourceView = jsCast<JSArrayBufferView*>(sourceArray);
if (UNLIKELY(sourceView->isNeutered()))
return throwVMTypeError(exec, scope, typedArrayBufferHasBeenDetachedErrorMessage);
length = jsCast<JSArrayBufferView*>(sourceArray)->length();
} else {
JSValue lengthValue = sourceArray->get(exec, vm.propertyNames->length);
RETURN_IF_EXCEPTION(scope, encodedJSValue());
length = lengthValue.toUInt32(exec);
}
RETURN_IF_EXCEPTION(scope, encodedJSValue());
scope.release();
thisObject->set(exec, offset, sourceArray, 0, length, CopyType::Unobservable);
return JSValue::encode(jsUndefined());
}
template<typename ViewClass>
EncodedJSValue JSC_HOST_CALL genericTypedArrayViewProtoFuncCopyWithin(VM& vm, ExecState* exec)
{
auto scope = DECLARE_THROW_SCOPE(vm);
ViewClass* thisObject = jsCast<ViewClass*>(exec->thisValue());
if (thisObject->isNeutered())
return throwVMTypeError(exec, scope, typedArrayBufferHasBeenDetachedErrorMessage);
long length = thisObject->length();
long to = argumentClampedIndexFromStartOrEnd(exec, 0, length);
RETURN_IF_EXCEPTION(scope, encodedJSValue());
long from = argumentClampedIndexFromStartOrEnd(exec, 1, length);
RETURN_IF_EXCEPTION(scope, encodedJSValue());
long final = argumentClampedIndexFromStartOrEnd(exec, 2, length, length);
RETURN_IF_EXCEPTION(scope, encodedJSValue());
if (final < from)
return JSValue::encode(exec->thisValue());
long count = std::min(length - std::max(to, from), final - from);
if (thisObject->isNeutered())
return throwVMTypeError(exec, scope, typedArrayBufferHasBeenDetachedErrorMessage);
typename ViewClass::ElementType* array = thisObject->typedVector();
memmove(array + to, array + from, count * thisObject->elementSize);
return JSValue::encode(exec->thisValue());
}
template<typename ViewClass>
EncodedJSValue JSC_HOST_CALL genericTypedArrayViewProtoFuncIncludes(VM& vm, ExecState* exec)
{
auto scope = DECLARE_THROW_SCOPE(vm);
ViewClass* thisObject = jsCast<ViewClass*>(exec->thisValue());
if (thisObject->isNeutered())
return throwVMTypeError(exec, scope, typedArrayBufferHasBeenDetachedErrorMessage);
unsigned length = thisObject->length();
if (!length)
return JSValue::encode(jsBoolean(false));
JSValue valueToFind = exec->argument(0);
unsigned index = argumentClampedIndexFromStartOrEnd(exec, 1, length);
RETURN_IF_EXCEPTION(scope, encodedJSValue());
if (thisObject->isNeutered())
return throwVMTypeError(exec, scope, typedArrayBufferHasBeenDetachedErrorMessage);
typename ViewClass::ElementType* array = thisObject->typedVector();
auto targetOption = ViewClass::toAdaptorNativeFromValueWithoutCoercion(valueToFind);
if (!targetOption)
return JSValue::encode(jsBoolean(false));
scope.assertNoException();
RELEASE_ASSERT(!thisObject->isNeutered());
if (std::isnan(static_cast<double>(*targetOption))) {
for (; index < length; ++index) {
if (std::isnan(static_cast<double>(array[index])))
return JSValue::encode(jsBoolean(true));
}
} else {
for (; index < length; ++index) {
if (array[index] == targetOption)
return JSValue::encode(jsBoolean(true));
}
}
return JSValue::encode(jsBoolean(false));
}
template<typename ViewClass>
EncodedJSValue JSC_HOST_CALL genericTypedArrayViewProtoFuncIndexOf(VM& vm, ExecState* exec)
{
auto scope = DECLARE_THROW_SCOPE(vm);
ViewClass* thisObject = jsCast<ViewClass*>(exec->thisValue());
if (thisObject->isNeutered())
return throwVMTypeError(exec, scope, typedArrayBufferHasBeenDetachedErrorMessage);
if (!exec->argumentCount())
return throwVMTypeError(exec, scope, "Expected at least one argument"_s);
unsigned length = thisObject->length();
JSValue valueToFind = exec->argument(0);
unsigned index = argumentClampedIndexFromStartOrEnd(exec, 1, length);
RETURN_IF_EXCEPTION(scope, encodedJSValue());
if (thisObject->isNeutered())
return throwVMTypeError(exec, scope, typedArrayBufferHasBeenDetachedErrorMessage);
typename ViewClass::ElementType* array = thisObject->typedVector();
auto targetOption = ViewClass::toAdaptorNativeFromValueWithoutCoercion(valueToFind);
if (!targetOption)
return JSValue::encode(jsNumber(-1));
scope.assertNoException();
RELEASE_ASSERT(!thisObject->isNeutered());
for (; index < length; ++index) {
if (array[index] == targetOption)
return JSValue::encode(jsNumber(index));
}
return JSValue::encode(jsNumber(-1));
}
template<typename ViewClass>
EncodedJSValue JSC_HOST_CALL genericTypedArrayViewProtoFuncJoin(VM& vm, ExecState* exec)
{
auto scope = DECLARE_THROW_SCOPE(vm);
ViewClass* thisObject = jsCast<ViewClass*>(exec->thisValue());
if (thisObject->isNeutered())
return throwVMTypeError(exec, scope, typedArrayBufferHasBeenDetachedErrorMessage);
auto joinWithSeparator = [&] (StringView separator) -> EncodedJSValue {
ViewClass* thisObject = jsCast<ViewClass*>(exec->thisValue());
unsigned length = thisObject->length();
JSStringJoiner joiner(*exec, separator, length);
RETURN_IF_EXCEPTION(scope, encodedJSValue());
for (unsigned i = 0; i < length; i++) {
joiner.append(*exec, thisObject->getIndexQuickly(i));
RETURN_IF_EXCEPTION(scope, encodedJSValue());
}
scope.release();
return JSValue::encode(joiner.join(*exec));
};
JSValue separatorValue = exec->argument(0);
if (separatorValue.isUndefined()) {
const LChar* comma = reinterpret_cast<const LChar*>(",");
return joinWithSeparator({ comma, 1 });
}
JSString* separatorString = separatorValue.toString(exec);
RETURN_IF_EXCEPTION(scope, encodedJSValue());
if (thisObject->isNeutered())
return throwVMTypeError(exec, scope, typedArrayBufferHasBeenDetachedErrorMessage);
auto viewWithString = separatorString->viewWithUnderlyingString(exec);
RETURN_IF_EXCEPTION(scope, encodedJSValue());
return joinWithSeparator(viewWithString.view);
}
template<typename ViewClass>
EncodedJSValue JSC_HOST_CALL genericTypedArrayViewProtoFuncLastIndexOf(VM& vm, ExecState* exec)
{
auto scope = DECLARE_THROW_SCOPE(vm);
ViewClass* thisObject = jsCast<ViewClass*>(exec->thisValue());
if (thisObject->isNeutered())
return throwVMTypeError(exec, scope, typedArrayBufferHasBeenDetachedErrorMessage);
if (!exec->argumentCount())
return throwVMTypeError(exec, scope, "Expected at least one argument"_s);
unsigned length = thisObject->length();
JSValue valueToFind = exec->argument(0);
int index = length - 1;
if (exec->argumentCount() >= 2) {
JSValue fromValue = exec->uncheckedArgument(1);
double fromDouble = fromValue.toInteger(exec);
if (fromDouble < 0) {
fromDouble += length;
if (fromDouble < 0)
return JSValue::encode(jsNumber(-1));
}
if (fromDouble < length)
index = static_cast<unsigned>(fromDouble);
}
RETURN_IF_EXCEPTION(scope, encodedJSValue());
if (thisObject->isNeutered())
return throwVMTypeError(exec, scope, typedArrayBufferHasBeenDetachedErrorMessage);
auto targetOption = ViewClass::toAdaptorNativeFromValueWithoutCoercion(valueToFind);
if (!targetOption)
return JSValue::encode(jsNumber(-1));
typename ViewClass::ElementType* array = thisObject->typedVector();
scope.assertNoException();
RELEASE_ASSERT(!thisObject->isNeutered());
for (; index >= 0; --index) {
if (array[index] == targetOption)
return JSValue::encode(jsNumber(index));
}
return JSValue::encode(jsNumber(-1));
}
template<typename ViewClass>
EncodedJSValue JSC_HOST_CALL genericTypedArrayViewProtoGetterFuncBuffer(VM&, ExecState* exec)
{
ViewClass* thisObject = jsCast<ViewClass*>(exec->thisValue());
return JSValue::encode(thisObject->possiblySharedJSBuffer(exec));
}
template<typename ViewClass>
EncodedJSValue JSC_HOST_CALL genericTypedArrayViewProtoGetterFuncLength(VM&, ExecState* exec)
{
ViewClass* thisObject = jsCast<ViewClass*>(exec->thisValue());
return JSValue::encode(jsNumber(thisObject->length()));
}
template<typename ViewClass>
EncodedJSValue JSC_HOST_CALL genericTypedArrayViewProtoGetterFuncByteLength(VM&, ExecState* exec)
{
ViewClass* thisObject = jsCast<ViewClass*>(exec->thisValue());
return JSValue::encode(jsNumber(thisObject->byteLength()));
}
template<typename ViewClass>
EncodedJSValue JSC_HOST_CALL genericTypedArrayViewProtoGetterFuncByteOffset(VM&, ExecState* exec)
{
ViewClass* thisObject = jsCast<ViewClass*>(exec->thisValue());
return JSValue::encode(jsNumber(thisObject->byteOffset()));
}
template<typename ViewClass>
EncodedJSValue JSC_HOST_CALL genericTypedArrayViewProtoFuncReverse(VM& vm, ExecState* exec)
{
auto scope = DECLARE_THROW_SCOPE(vm);
ViewClass* thisObject = jsCast<ViewClass*>(exec->thisValue());
if (thisObject->isNeutered())
return throwVMTypeError(exec, scope, typedArrayBufferHasBeenDetachedErrorMessage);
typename ViewClass::ElementType* array = thisObject->typedVector();
std::reverse(array, array + thisObject->length());
return JSValue::encode(thisObject);
}
template<typename ViewClass>
EncodedJSValue JSC_HOST_CALL genericTypedArrayViewPrivateFuncSort(VM& vm, ExecState* exec)
{
auto scope = DECLARE_THROW_SCOPE(vm);
ViewClass* thisObject = jsCast<ViewClass*>(exec->argument(0));
if (thisObject->isNeutered())
return throwVMTypeError(exec, scope, typedArrayBufferHasBeenDetachedErrorMessage);
thisObject->sort();
return JSValue::encode(thisObject);
}
template<typename ViewClass>
EncodedJSValue JSC_HOST_CALL genericTypedArrayViewProtoFuncSlice(VM& vm, ExecState* exec)
{
auto scope = DECLARE_THROW_SCOPE(vm);
JSFunction* callee = jsCast<JSFunction*>(exec->jsCallee());
ViewClass* thisObject = jsCast<ViewClass*>(exec->thisValue());
if (thisObject->isNeutered())
return throwVMTypeError(exec, scope, typedArrayBufferHasBeenDetachedErrorMessage);
unsigned thisLength = thisObject->length();
unsigned begin = argumentClampedIndexFromStartOrEnd(exec, 0, thisLength);
RETURN_IF_EXCEPTION(scope, encodedJSValue());
unsigned end = argumentClampedIndexFromStartOrEnd(exec, 1, thisLength, thisLength);
RETURN_IF_EXCEPTION(scope, encodedJSValue());
if (thisObject->isNeutered())
return throwVMTypeError(exec, scope, typedArrayBufferHasBeenDetachedErrorMessage);
end = std::max(begin, end);
ASSERT(end >= begin);
unsigned length = end - begin;
MarkedArgumentBuffer args;
args.append(jsNumber(length));
ASSERT(!args.hasOverflowed());
JSArrayBufferView* result = speciesConstruct(exec, thisObject, args, [&]() {
Structure* structure = callee->globalObject(vm)->typedArrayStructure(ViewClass::TypedArrayStorageType);
return ViewClass::createUninitialized(exec, structure, length);
});
RETURN_IF_EXCEPTION(scope, encodedJSValue());
ASSERT(!result->isNeutered());
if (thisObject->isNeutered())
return throwVMTypeError(exec, scope, typedArrayBufferHasBeenDetachedErrorMessage);
if (!length)
return JSValue::encode(result);
length = std::min(length, result->length());
switch (result->classInfo(vm)->typedArrayStorageType) {
case TypeInt8:
scope.release();
jsCast<JSInt8Array*>(result)->set(exec, 0, thisObject, begin, length, CopyType::LeftToRight);
return JSValue::encode(result);
case TypeInt16:
scope.release();
jsCast<JSInt16Array*>(result)->set(exec, 0, thisObject, begin, length, CopyType::LeftToRight);
return JSValue::encode(result);
case TypeInt32:
scope.release();
jsCast<JSInt32Array*>(result)->set(exec, 0, thisObject, begin, length, CopyType::LeftToRight);
return JSValue::encode(result);
case TypeUint8:
scope.release();
jsCast<JSUint8Array*>(result)->set(exec, 0, thisObject, begin, length, CopyType::LeftToRight);
return JSValue::encode(result);
case TypeUint8Clamped:
scope.release();
jsCast<JSUint8ClampedArray*>(result)->set(exec, 0, thisObject, begin, length, CopyType::LeftToRight);
return JSValue::encode(result);
case TypeUint16:
scope.release();
jsCast<JSUint16Array*>(result)->set(exec, 0, thisObject, begin, length, CopyType::LeftToRight);
return JSValue::encode(result);
case TypeUint32:
scope.release();
jsCast<JSUint32Array*>(result)->set(exec, 0, thisObject, begin, length, CopyType::LeftToRight);
return JSValue::encode(result);
case TypeFloat32:
scope.release();
jsCast<JSFloat32Array*>(result)->set(exec, 0, thisObject, begin, length, CopyType::LeftToRight);
return JSValue::encode(result);
case TypeFloat64:
scope.release();
jsCast<JSFloat64Array*>(result)->set(exec, 0, thisObject, begin, length, CopyType::LeftToRight);
return JSValue::encode(result);
default:
RELEASE_ASSERT_NOT_REACHED();
}
}
template<typename ViewClass>
EncodedJSValue JSC_HOST_CALL genericTypedArrayViewPrivateFuncSubarrayCreate(VM&vm, ExecState* exec)
{
auto scope = DECLARE_THROW_SCOPE(vm);
JSFunction* callee = jsCast<JSFunction*>(exec->jsCallee());
ViewClass* thisObject = jsCast<ViewClass*>(exec->thisValue());
if (thisObject->isNeutered())
return throwVMTypeError(exec, scope, typedArrayBufferHasBeenDetachedErrorMessage);
unsigned thisLength = thisObject->length();
ASSERT(exec->argument(0).isNumber());
ASSERT(exec->argument(1).isUndefined() || exec->argument(1).isNumber());
unsigned begin = argumentClampedIndexFromStartOrEnd(exec, 0, thisLength);
scope.assertNoException();
unsigned end = argumentClampedIndexFromStartOrEnd(exec, 1, thisLength, thisLength);
scope.assertNoException();
RELEASE_ASSERT(!thisObject->isNeutered());
end = std::max(begin, end);
ASSERT(end >= begin);
unsigned offset = begin;
unsigned length = end - begin;
RefPtr<ArrayBuffer> arrayBuffer = thisObject->possiblySharedBuffer();
RELEASE_ASSERT(thisLength == thisObject->length());
unsigned newByteOffset = thisObject->byteOffset() + offset * ViewClass::elementSize;
JSObject* defaultConstructor = callee->globalObject(vm)->typedArrayConstructor(ViewClass::TypedArrayStorageType);
JSValue species = exec->uncheckedArgument(2);
if (species == defaultConstructor) {
Structure* structure = callee->globalObject(vm)->typedArrayStructure(ViewClass::TypedArrayStorageType);
scope.release();
return JSValue::encode(ViewClass::create(
exec, structure, WTFMove(arrayBuffer),
thisObject->byteOffset() + offset * ViewClass::elementSize,
length));
}
MarkedArgumentBuffer args;
args.append(vm.m_typedArrayController->toJS(exec, thisObject->globalObject(vm), arrayBuffer.get()));
args.append(jsNumber(newByteOffset));
args.append(jsNumber(length));
ASSERT(!args.hasOverflowed());
JSObject* result = construct(exec, species, args, "species is not a constructor");
RETURN_IF_EXCEPTION(scope, encodedJSValue());
if (jsDynamicCast<JSArrayBufferView*>(vm, result))
return JSValue::encode(result);
throwTypeError(exec, scope, "species constructor did not return a TypedArray View"_s);
return JSValue::encode(JSValue());
}
}