/* * Copyright (C) 2016-2017 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 INC. AND ITS CONTRIBUTORS ``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 INC. OR ITS 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. */ #pragma once #include "IDLTypes.h" #include "JSDOMConvertBase.h" #include "JSDOMConvertNumbers.h" #include "JSDOMGlobalObject.h" #include "JavaScriptCore/IteratorOperations.h" #include "JavaScriptCore/JSArray.h" #include "JavaScriptCore/JSGlobalObjectInlines.h" #include "JavaScriptCore/ObjectConstructor.h" namespace WebCore { namespace Detail { template struct GenericSequenceConverter { using ReturnType = Vector; static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSObject* object) { return convert(lexicalGlobalObject, object, ReturnType()); } static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSObject* object, ReturnType&& result) { forEachInIterable(&lexicalGlobalObject, object, [&result](JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSValue nextValue) { auto scope = DECLARE_THROW_SCOPE(vm); auto convertedValue = Converter::convert(*lexicalGlobalObject, nextValue); if (UNLIKELY(scope.exception())) return; result.append(WTFMove(convertedValue)); }); return WTFMove(result); } static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSObject* object, JSC::JSValue method) { return convert(lexicalGlobalObject, object, method, ReturnType()); } static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSObject* object, JSC::JSValue method, ReturnType&& result) { forEachInIterable(lexicalGlobalObject, object, method, [&result](JSC::VM& vm, JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSValue nextValue) { auto scope = DECLARE_THROW_SCOPE(vm); auto convertedValue = Converter::convert(lexicalGlobalObject, nextValue); if (UNLIKELY(scope.exception())) return; result.append(WTFMove(convertedValue)); }); return WTFMove(result); } }; // Specialization for numeric types // FIXME: This is only implemented for the IDLFloatingPointTypes and IDLLong. To add // support for more numeric types, add an overload of Converter::convert that // takes a JSGlobalObject, ThrowScope and double as its arguments. template struct NumericSequenceConverter { using GenericConverter = GenericSequenceConverter; using ReturnType = typename GenericConverter::ReturnType; static ReturnType convertArray(JSC::JSGlobalObject& lexicalGlobalObject, JSC::ThrowScope& scope, JSC::JSArray* array, unsigned length, JSC::IndexingType indexingType, ReturnType&& result) { if (indexingType == JSC::Int32Shape) { for (unsigned i = 0; i < length; i++) { auto indexValue = array->butterfly()->contiguousInt32().at(array, i).get(); ASSERT(!indexValue || indexValue.isInt32()); if (!indexValue) result.uncheckedAppend(0); else result.uncheckedAppend(indexValue.asInt32()); } return WTFMove(result); } ASSERT(indexingType == JSC::DoubleShape); for (unsigned i = 0; i < length; i++) { double doubleValue = array->butterfly()->contiguousDouble().at(array, i); if (std::isnan(doubleValue)) result.uncheckedAppend(0); else { auto convertedValue = Converter::convert(lexicalGlobalObject, scope, doubleValue); RETURN_IF_EXCEPTION(scope, {}); result.uncheckedAppend(convertedValue); } } return WTFMove(result); } static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSValue value) { auto& vm = JSC::getVM(&lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); if (!value.isObject()) { throwSequenceTypeError(lexicalGlobalObject, scope); return {}; } JSC::JSObject* object = JSC::asObject(value); if (!JSC::isJSArray(object)) RELEASE_AND_RETURN(scope, GenericConverter::convert(lexicalGlobalObject, object)); JSC::JSArray* array = JSC::asArray(object); if (!array->isIteratorProtocolFastAndNonObservable()) RELEASE_AND_RETURN(scope, GenericConverter::convert(lexicalGlobalObject, object)); unsigned length = array->length(); ReturnType result; // If we're not an int32/double array, it's possible that converting a // JSValue to a number could cause the iterator protocol to change, hence, // we may need more capacity, or less. In such cases, we use the length // as a proxy for the capacity we will most likely need (it's unlikely that // a program is written with a valueOf that will augment the iterator protocol). // If we are an int32/double array, then length is precisely the capacity we need. if (!result.tryReserveCapacity(length)) { // FIXME: Is the right exception to throw? throwTypeError(&lexicalGlobalObject, scope); return {}; } JSC::IndexingType indexingType = array->indexingType() & JSC::IndexingShapeMask; if (indexingType != JSC::Int32Shape && indexingType != JSC::DoubleShape) RELEASE_AND_RETURN(scope, GenericConverter::convert(lexicalGlobalObject, object, WTFMove(result))); return convertArray(lexicalGlobalObject, scope, array, length, indexingType, WTFMove(result)); } static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSObject* object, JSC::JSValue method) { auto& vm = JSC::getVM(&lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); if (!JSC::isJSArray(object)) RELEASE_AND_RETURN(scope, GenericConverter::convert(lexicalGlobalObject, object, method)); JSC::JSArray* array = JSC::asArray(object); if (!array->isIteratorProtocolFastAndNonObservable()) RELEASE_AND_RETURN(scope, GenericConverter::convert(lexicalGlobalObject, object, method)); unsigned length = array->length(); ReturnType result; // If we're not an int32/double array, it's possible that converting a // JSValue to a number could cause the iterator protocol to change, hence, // we may need more capacity, or less. In such cases, we use the length // as a proxy for the capacity we will most likely need (it's unlikely that // a program is written with a valueOf that will augment the iterator protocol). // If we are an int32/double array, then length is precisely the capacity we need. if (!result.tryReserveCapacity(length)) { // FIXME: Is the right exception to throw? throwTypeError(&lexicalGlobalObject, scope); return {}; } JSC::IndexingType indexingType = array->indexingType() & JSC::IndexingShapeMask; if (indexingType != JSC::Int32Shape && indexingType != JSC::DoubleShape) RELEASE_AND_RETURN(scope, GenericConverter::convert(lexicalGlobalObject, object, method, WTFMove(result))); return convertArray(lexicalGlobalObject, scope, array, length, indexingType, WTFMove(result)); } }; template struct SequenceConverter { using GenericConverter = GenericSequenceConverter; using ReturnType = typename GenericConverter::ReturnType; static ReturnType convertArray(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSArray* array) { auto& vm = lexicalGlobalObject.vm(); auto scope = DECLARE_THROW_SCOPE(vm); unsigned length = array->length(); ReturnType result; if (!result.tryReserveCapacity(length)) { // FIXME: Is the right exception to throw? throwTypeError(&lexicalGlobalObject, scope); return {}; } JSC::IndexingType indexingType = array->indexingType() & JSC::IndexingShapeMask; if (indexingType == JSC::ContiguousShape) { for (unsigned i = 0; i < length; i++) { auto indexValue = array->butterfly()->contiguous().at(array, i).get(); if (!indexValue) indexValue = JSC::jsUndefined(); auto convertedValue = Converter::convert(lexicalGlobalObject, indexValue); RETURN_IF_EXCEPTION(scope, {}); result.uncheckedAppend(convertedValue); } return result; } for (unsigned i = 0; i < length; i++) { auto indexValue = array->getDirectIndex(&lexicalGlobalObject, i); RETURN_IF_EXCEPTION(scope, {}); if (!indexValue) indexValue = JSC::jsUndefined(); auto convertedValue = Converter::convert(lexicalGlobalObject, indexValue); RETURN_IF_EXCEPTION(scope, {}); result.uncheckedAppend(convertedValue); } return result; } static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSValue value) { auto& vm = JSC::getVM(&lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); if (!value.isObject()) { throwSequenceTypeError(lexicalGlobalObject, scope); return {}; } JSC::JSObject* object = JSC::asObject(value); if (Converter::conversionHasSideEffects) RELEASE_AND_RETURN(scope, (GenericConverter::convert(lexicalGlobalObject, object))); if (!JSC::isJSArray(object)) RELEASE_AND_RETURN(scope, (GenericConverter::convert(lexicalGlobalObject, object))); JSC::JSArray* array = JSC::asArray(object); if (!array->isIteratorProtocolFastAndNonObservable()) RELEASE_AND_RETURN(scope, (GenericConverter::convert(lexicalGlobalObject, object))); RELEASE_AND_RETURN(scope, (convertArray(lexicalGlobalObject, array))); } static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSObject* object, JSC::JSValue method) { if (Converter::conversionHasSideEffects) return GenericConverter::convert(lexicalGlobalObject, object, method); if (!JSC::isJSArray(object)) return GenericConverter::convert(lexicalGlobalObject, object, method); JSC::JSArray* array = JSC::asArray(object); if (!array->isIteratorProtocolFastAndNonObservable()) return GenericConverter::convert(lexicalGlobalObject, object, method); return convertArray(lexicalGlobalObject, array); } }; template<> struct SequenceConverter { using ReturnType = typename GenericSequenceConverter::ReturnType; static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSValue value) { return NumericSequenceConverter::convert(lexicalGlobalObject, value); } static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSObject* object, JSC::JSValue method) { return NumericSequenceConverter::convert(lexicalGlobalObject, object, method); } }; template<> struct SequenceConverter { using ReturnType = typename GenericSequenceConverter::ReturnType; static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSValue value) { return NumericSequenceConverter::convert(lexicalGlobalObject, value); } static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSObject* object, JSC::JSValue method) { return NumericSequenceConverter::convert(lexicalGlobalObject, object, method); } }; template<> struct SequenceConverter { using ReturnType = typename GenericSequenceConverter::ReturnType; static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSValue value) { return NumericSequenceConverter::convert(lexicalGlobalObject, value); } static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSObject* object, JSC::JSValue method) { return NumericSequenceConverter::convert(lexicalGlobalObject, object, method); } }; template<> struct SequenceConverter { using ReturnType = typename GenericSequenceConverter::ReturnType; static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSValue value) { return NumericSequenceConverter::convert(lexicalGlobalObject, value); } static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSObject* object, JSC::JSValue method) { return NumericSequenceConverter::convert(lexicalGlobalObject, object, method); } }; template<> struct SequenceConverter { using ReturnType = typename GenericSequenceConverter::ReturnType; static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSValue value) { return NumericSequenceConverter::convert(lexicalGlobalObject, value); } static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSObject* object, JSC::JSValue method) { return NumericSequenceConverter::convert(lexicalGlobalObject, object, method); } }; } template struct Converter> : DefaultConverter> { using ReturnType = typename Detail::SequenceConverter::ReturnType; static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSValue value) { return Detail::SequenceConverter::convert(lexicalGlobalObject, value); } static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSObject* object, JSC::JSValue method) { return Detail::SequenceConverter::convert(lexicalGlobalObject, object, method); } }; template struct JSConverter> { static constexpr bool needsState = true; static constexpr bool needsGlobalObject = true; template static JSC::JSValue convert(JSC::JSGlobalObject& lexicalGlobalObject, JSDOMGlobalObject& globalObject, const Vector& vector) { JSC::VM& vm = JSC::getVM(&lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSC::MarkedArgumentBuffer list; for (auto& element : vector) { auto jsValue = toJS(lexicalGlobalObject, globalObject, element); RETURN_IF_EXCEPTION(scope, {}); list.append(jsValue); } if (UNLIKELY(list.hasOverflowed())) { throwOutOfMemoryError(&lexicalGlobalObject, scope); return {}; } RELEASE_AND_RETURN(scope, JSC::constructArray(&globalObject, static_cast(nullptr), list)); } }; template struct Converter> : DefaultConverter> { using ReturnType = typename Detail::SequenceConverter::ReturnType; static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSValue value) { return Detail::SequenceConverter::convert(lexicalGlobalObject, value); } static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSObject* object, JSC::JSValue method) { return Detail::SequenceConverter::convert(lexicalGlobalObject, object, method); } }; template struct JSConverter> { static constexpr bool needsState = true; static constexpr bool needsGlobalObject = true; template static JSC::JSValue convert(JSC::JSGlobalObject& lexicalGlobalObject, JSDOMGlobalObject& globalObject, const Vector& vector) { JSC::VM& vm = JSC::getVM(&lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSC::MarkedArgumentBuffer list; for (auto& element : vector) { auto jsValue = toJS(lexicalGlobalObject, globalObject, element); RETURN_IF_EXCEPTION(scope, {}); list.append(jsValue); } if (UNLIKELY(list.hasOverflowed())) { throwOutOfMemoryError(&lexicalGlobalObject, scope); return {}; } auto* array = JSC::constructArray(&globalObject, static_cast(nullptr), list); RETURN_IF_EXCEPTION(scope, {}); RELEASE_AND_RETURN(scope, JSC::objectConstructorFreeze(&lexicalGlobalObject, array)); } }; } // namespace WebCore