1/*
2 * Copyright (C) 2016-2017 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. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#pragma once
27
28#include "IDLTypes.h"
29#include "JSDOMConvertBase.h"
30#include "JSDOMConvertNumbers.h"
31#include "JSDOMGlobalObject.h"
32#include <JavaScriptCore/IteratorOperations.h>
33#include <JavaScriptCore/JSArray.h>
34#include <JavaScriptCore/JSGlobalObjectInlines.h>
35#include <JavaScriptCore/ObjectConstructor.h>
36
37namespace WebCore {
38
39namespace Detail {
40
41template<typename IDLType>
42struct GenericSequenceConverter {
43 using ReturnType = Vector<typename IDLType::ImplementationType>;
44
45 static ReturnType convert(JSC::ExecState& state, JSC::JSObject* object)
46 {
47 return convert(state, object, ReturnType());
48 }
49
50 static ReturnType convert(JSC::ExecState& state, JSC::JSObject* object, ReturnType&& result)
51 {
52 forEachInIterable(&state, object, [&result](JSC::VM& vm, JSC::ExecState* state, JSC::JSValue nextValue) {
53 auto scope = DECLARE_THROW_SCOPE(vm);
54
55 auto convertedValue = Converter<IDLType>::convert(*state, nextValue);
56 if (UNLIKELY(scope.exception()))
57 return;
58 result.append(WTFMove(convertedValue));
59 });
60 return WTFMove(result);
61 }
62
63 static ReturnType convert(JSC::ExecState& state, JSC::JSObject* object, JSC::JSValue method)
64 {
65 return convert(state, object, method, ReturnType());
66 }
67
68 static ReturnType convert(JSC::ExecState& state, JSC::JSObject* object, JSC::JSValue method, ReturnType&& result)
69 {
70 forEachInIterable(state, object, method, [&result](JSC::VM& vm, JSC::ExecState& state, JSC::JSValue nextValue) {
71 auto scope = DECLARE_THROW_SCOPE(vm);
72
73 auto convertedValue = Converter<IDLType>::convert(state, nextValue);
74 if (UNLIKELY(scope.exception()))
75 return;
76 result.append(WTFMove(convertedValue));
77 });
78 return WTFMove(result);
79 }
80};
81
82// Specialization for numeric types
83// FIXME: This is only implemented for the IDLFloatingPointTypes and IDLLong. To add
84// support for more numeric types, add an overload of Converter<IDLType>::convert that
85// takes an ExecState, ThrowScope, double as its arguments.
86template<typename IDLType>
87struct NumericSequenceConverter {
88 using GenericConverter = GenericSequenceConverter<IDLType>;
89 using ReturnType = typename GenericConverter::ReturnType;
90
91 static ReturnType convertArray(JSC::ExecState& state, JSC::ThrowScope& scope, JSC::JSArray* array, unsigned length, JSC::IndexingType indexingType, ReturnType&& result)
92 {
93 if (indexingType == JSC::Int32Shape) {
94 for (unsigned i = 0; i < length; i++) {
95 auto indexValue = array->butterfly()->contiguousInt32().at(array, i).get();
96 ASSERT(!indexValue || indexValue.isInt32());
97 if (!indexValue)
98 result.uncheckedAppend(0);
99 else
100 result.uncheckedAppend(indexValue.asInt32());
101 }
102 return WTFMove(result);
103 }
104
105 ASSERT(indexingType == JSC::DoubleShape);
106 for (unsigned i = 0; i < length; i++) {
107 double doubleValue = array->butterfly()->contiguousDouble().at(array, i);
108 if (std::isnan(doubleValue))
109 result.uncheckedAppend(0);
110 else {
111 auto convertedValue = Converter<IDLType>::convert(state, scope, doubleValue);
112 RETURN_IF_EXCEPTION(scope, { });
113
114 result.uncheckedAppend(convertedValue);
115 }
116 }
117 return WTFMove(result);
118 }
119
120 static ReturnType convert(JSC::ExecState& state, JSC::JSValue value)
121 {
122 auto& vm = state.vm();
123 auto scope = DECLARE_THROW_SCOPE(vm);
124
125 if (!value.isObject()) {
126 throwSequenceTypeError(state, scope);
127 return { };
128 }
129
130 JSC::JSObject* object = JSC::asObject(value);
131 if (!JSC::isJSArray(object))
132 return GenericConverter::convert(state, object);
133
134 JSC::JSArray* array = JSC::asArray(object);
135 if (!array->isIteratorProtocolFastAndNonObservable())
136 return GenericConverter::convert(state, object);
137
138 unsigned length = array->length();
139 ReturnType result;
140 // If we're not an int32/double array, it's possible that converting a
141 // JSValue to a number could cause the iterator protocol to change, hence,
142 // we may need more capacity, or less. In such cases, we use the length
143 // as a proxy for the capacity we will most likely need (it's unlikely that
144 // a program is written with a valueOf that will augment the iterator protocol).
145 // If we are an int32/double array, then length is precisely the capacity we need.
146 if (!result.tryReserveCapacity(length)) {
147 // FIXME: Is the right exception to throw?
148 throwTypeError(&state, scope);
149 return { };
150 }
151
152 JSC::IndexingType indexingType = array->indexingType() & JSC::IndexingShapeMask;
153 if (indexingType != JSC::Int32Shape && indexingType != JSC::DoubleShape)
154 return GenericConverter::convert(state, object, WTFMove(result));
155
156 return convertArray(state, scope, array, length, indexingType, WTFMove(result));
157 }
158
159 static ReturnType convert(JSC::ExecState& state, JSC::JSObject* object, JSC::JSValue method)
160 {
161 auto& vm = state.vm();
162 auto scope = DECLARE_THROW_SCOPE(vm);
163
164 if (!JSC::isJSArray(object))
165 return GenericConverter::convert(state, object, method);
166
167 JSC::JSArray* array = JSC::asArray(object);
168 if (!array->isIteratorProtocolFastAndNonObservable())
169 return GenericConverter::convert(state, object, method);
170
171 unsigned length = array->length();
172 ReturnType result;
173 // If we're not an int32/double array, it's possible that converting a
174 // JSValue to a number could cause the iterator protocol to change, hence,
175 // we may need more capacity, or less. In such cases, we use the length
176 // as a proxy for the capacity we will most likely need (it's unlikely that
177 // a program is written with a valueOf that will augment the iterator protocol).
178 // If we are an int32/double array, then length is precisely the capacity we need.
179 if (!result.tryReserveCapacity(length)) {
180 // FIXME: Is the right exception to throw?
181 throwTypeError(&state, scope);
182 return { };
183 }
184
185 JSC::IndexingType indexingType = array->indexingType() & JSC::IndexingShapeMask;
186 if (indexingType != JSC::Int32Shape && indexingType != JSC::DoubleShape)
187 return GenericConverter::convert(state, object, method, WTFMove(result));
188
189 return convertArray(state, scope, array, length, indexingType, WTFMove(result));
190 }
191};
192
193template<typename IDLType>
194struct SequenceConverter {
195 using GenericConverter = GenericSequenceConverter<IDLType>;
196 using ReturnType = typename GenericConverter::ReturnType;
197
198 static ReturnType convertArray(JSC::ExecState& state, JSC::ThrowScope& scope, JSC::JSArray* array)
199 {
200 unsigned length = array->length();
201
202 ReturnType result;
203 if (!result.tryReserveCapacity(length)) {
204 // FIXME: Is the right exception to throw?
205 throwTypeError(&state, scope);
206 return { };
207 }
208
209 JSC::IndexingType indexingType = array->indexingType() & JSC::IndexingShapeMask;
210
211 if (indexingType == JSC::ContiguousShape) {
212 for (unsigned i = 0; i < length; i++) {
213 auto indexValue = array->butterfly()->contiguous().at(array, i).get();
214 if (!indexValue)
215 indexValue = JSC::jsUndefined();
216
217 auto convertedValue = Converter<IDLType>::convert(state, indexValue);
218 RETURN_IF_EXCEPTION(scope, { });
219
220 result.uncheckedAppend(convertedValue);
221 }
222 return result;
223 }
224
225 for (unsigned i = 0; i < length; i++) {
226 auto indexValue = array->getDirectIndex(&state, i);
227 RETURN_IF_EXCEPTION(scope, { });
228
229 if (!indexValue)
230 indexValue = JSC::jsUndefined();
231
232 auto convertedValue = Converter<IDLType>::convert(state, indexValue);
233 RETURN_IF_EXCEPTION(scope, { });
234
235 result.uncheckedAppend(convertedValue);
236 }
237 return result;
238 }
239
240 static ReturnType convert(JSC::ExecState& state, JSC::JSValue value)
241 {
242 auto& vm = state.vm();
243 auto scope = DECLARE_THROW_SCOPE(vm);
244
245 if (!value.isObject()) {
246 throwSequenceTypeError(state, scope);
247 return { };
248 }
249
250 JSC::JSObject* object = JSC::asObject(value);
251 if (Converter<IDLType>::conversionHasSideEffects)
252 return GenericConverter::convert(state, object);
253
254 if (!JSC::isJSArray(object))
255 return GenericConverter::convert(state, object);
256
257 JSC::JSArray* array = JSC::asArray(object);
258 if (!array->isIteratorProtocolFastAndNonObservable())
259 return GenericConverter::convert(state, object);
260
261 return convertArray(state, scope, array);
262 }
263
264 static ReturnType convert(JSC::ExecState& state, JSC::JSObject* object, JSC::JSValue method)
265 {
266 auto& vm = state.vm();
267 auto scope = DECLARE_THROW_SCOPE(vm);
268
269 if (Converter<IDLType>::conversionHasSideEffects)
270 return GenericConverter::convert(state, object, method);
271
272 if (!JSC::isJSArray(object))
273 return GenericConverter::convert(state, object, method);
274
275 JSC::JSArray* array = JSC::asArray(object);
276 if (!array->isIteratorProtocolFastAndNonObservable())
277 return GenericConverter::convert(state, object, method);
278
279 return convertArray(state, scope, array);
280 }
281};
282
283template<>
284struct SequenceConverter<IDLLong> {
285 using ReturnType = typename GenericSequenceConverter<IDLLong>::ReturnType;
286
287 static ReturnType convert(JSC::ExecState& state, JSC::JSValue value)
288 {
289 return NumericSequenceConverter<IDLLong>::convert(state, value);
290 }
291
292 static ReturnType convert(JSC::ExecState& state, JSC::JSObject* object, JSC::JSValue method)
293 {
294 return NumericSequenceConverter<IDLLong>::convert(state, object, method);
295 }
296};
297
298template<>
299struct SequenceConverter<IDLFloat> {
300 using ReturnType = typename GenericSequenceConverter<IDLFloat>::ReturnType;
301
302 static ReturnType convert(JSC::ExecState& state, JSC::JSValue value)
303 {
304 return NumericSequenceConverter<IDLFloat>::convert(state, value);
305 }
306
307 static ReturnType convert(JSC::ExecState& state, JSC::JSObject* object, JSC::JSValue method)
308 {
309 return NumericSequenceConverter<IDLFloat>::convert(state, object, method);
310 }
311};
312
313template<>
314struct SequenceConverter<IDLUnrestrictedFloat> {
315 using ReturnType = typename GenericSequenceConverter<IDLUnrestrictedFloat>::ReturnType;
316
317 static ReturnType convert(JSC::ExecState& state, JSC::JSValue value)
318 {
319 return NumericSequenceConverter<IDLUnrestrictedFloat>::convert(state, value);
320 }
321
322 static ReturnType convert(JSC::ExecState& state, JSC::JSObject* object, JSC::JSValue method)
323 {
324 return NumericSequenceConverter<IDLUnrestrictedFloat>::convert(state, object, method);
325 }
326};
327
328template<>
329struct SequenceConverter<IDLDouble> {
330 using ReturnType = typename GenericSequenceConverter<IDLDouble>::ReturnType;
331
332 static ReturnType convert(JSC::ExecState& state, JSC::JSValue value)
333 {
334 return NumericSequenceConverter<IDLDouble>::convert(state, value);
335 }
336
337 static ReturnType convert(JSC::ExecState& state, JSC::JSObject* object, JSC::JSValue method)
338 {
339 return NumericSequenceConverter<IDLDouble>::convert(state, object, method);
340 }
341};
342
343template<>
344struct SequenceConverter<IDLUnrestrictedDouble> {
345 using ReturnType = typename GenericSequenceConverter<IDLUnrestrictedDouble>::ReturnType;
346
347 static ReturnType convert(JSC::ExecState& state, JSC::JSValue value)
348 {
349 return NumericSequenceConverter<IDLUnrestrictedDouble>::convert(state, value);
350 }
351
352 static ReturnType convert(JSC::ExecState& state, JSC::JSObject* object, JSC::JSValue method)
353 {
354 return NumericSequenceConverter<IDLUnrestrictedDouble>::convert(state, object, method);
355 }
356};
357
358}
359
360template<typename T> struct Converter<IDLSequence<T>> : DefaultConverter<IDLSequence<T>> {
361 using ReturnType = typename Detail::SequenceConverter<T>::ReturnType;
362
363 static ReturnType convert(JSC::ExecState& state, JSC::JSValue value)
364 {
365 return Detail::SequenceConverter<T>::convert(state, value);
366 }
367
368 static ReturnType convert(JSC::ExecState& state, JSC::JSObject* object, JSC::JSValue method)
369 {
370 return Detail::SequenceConverter<T>::convert(state, object, method);
371 }
372};
373
374template<typename T> struct JSConverter<IDLSequence<T>> {
375 static constexpr bool needsState = true;
376 static constexpr bool needsGlobalObject = true;
377
378 template<typename U, size_t inlineCapacity>
379 static JSC::JSValue convert(JSC::ExecState& exec, JSDOMGlobalObject& globalObject, const Vector<U, inlineCapacity>& vector)
380 {
381 JSC::VM& vm = exec.vm();
382 auto scope = DECLARE_THROW_SCOPE(vm);
383 JSC::MarkedArgumentBuffer list;
384 for (auto& element : vector)
385 list.append(toJS<T>(exec, globalObject, element));
386 if (UNLIKELY(list.hasOverflowed())) {
387 throwOutOfMemoryError(&exec, scope);
388 return { };
389 }
390 return JSC::constructArray(&exec, nullptr, &globalObject, list);
391 }
392};
393
394template<typename T> struct Converter<IDLFrozenArray<T>> : DefaultConverter<IDLFrozenArray<T>> {
395 using ReturnType = typename Detail::SequenceConverter<T>::ReturnType;
396
397 static ReturnType convert(JSC::ExecState& state, JSC::JSValue value)
398 {
399 return Detail::SequenceConverter<T>::convert(state, value);
400 }
401
402 static ReturnType convert(JSC::ExecState& state, JSC::JSObject* object, JSC::JSValue method)
403 {
404 return Detail::SequenceConverter<T>::convert(state, object, method);
405 }
406};
407
408template<typename T> struct JSConverter<IDLFrozenArray<T>> {
409 static constexpr bool needsState = true;
410 static constexpr bool needsGlobalObject = true;
411
412 template<typename U, size_t inlineCapacity>
413 static JSC::JSValue convert(JSC::ExecState& exec, JSDOMGlobalObject& globalObject, const Vector<U, inlineCapacity>& vector)
414 {
415 JSC::VM& vm = exec.vm();
416 auto scope = DECLARE_THROW_SCOPE(vm);
417 JSC::MarkedArgumentBuffer list;
418 for (auto& element : vector)
419 list.append(toJS<T>(exec, globalObject, element));
420 if (UNLIKELY(list.hasOverflowed())) {
421 throwOutOfMemoryError(&exec, scope);
422 return { };
423 }
424 auto* array = JSC::constructArray(&exec, nullptr, &globalObject, list);
425 return JSC::objectConstructorFreeze(&exec, array);
426 }
427};
428
429} // namespace WebCore
430
431