df45fc0dc89ee9e576093e4506f57a94269651d6
[WebKit-https.git] / Source / JavaScriptCore / API / JSValue.mm
1 /*
2  * Copyright (C) 2013-2018 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. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27
28 #import "APICast.h"
29 #import "DateInstance.h"
30 #import "Error.h"
31 #import "Exception.h"
32 #import "JavaScriptCore.h"
33 #import "JSContextInternal.h"
34 #import "JSVirtualMachineInternal.h"
35 #import "JSValueInternal.h"
36 #import "JSWrapperMap.h"
37 #import "ObjcRuntimeExtras.h"
38 #import "JSCInlines.h"
39 #import "JSCJSValue.h"
40 #import "Strong.h"
41 #import "StrongInlines.h"
42 #import <wtf/Expected.h>
43 #import <wtf/HashMap.h>
44 #import <wtf/HashSet.h>
45 #import <wtf/Lock.h>
46 #import <wtf/ObjcRuntimeExtras.h>
47 #import <wtf/Vector.h>
48 #import <wtf/text/WTFString.h>
49 #import <wtf/text/StringHash.h>
50
51 #if ENABLE(REMOTE_INSPECTOR)
52 #import "CallFrame.h"
53 #import "JSGlobalObject.h"
54 #import "JSGlobalObjectInspectorController.h"
55 #endif
56
57 #if JSC_OBJC_API_ENABLED
58
59 NSString * const JSPropertyDescriptorWritableKey = @"writable";
60 NSString * const JSPropertyDescriptorEnumerableKey = @"enumerable";
61 NSString * const JSPropertyDescriptorConfigurableKey = @"configurable";
62 NSString * const JSPropertyDescriptorValueKey = @"value";
63 NSString * const JSPropertyDescriptorGetKey = @"get";
64 NSString * const JSPropertyDescriptorSetKey = @"set";
65
66 @implementation JSValue {
67     JSValueRef m_value;
68 }
69
70 - (void)dealloc
71 {
72     JSValueUnprotect([_context JSGlobalContextRef], m_value);
73     [_context release];
74     _context = nil;
75     [super dealloc];
76 }
77
78 - (NSString *)description
79 {
80     if (id wrapped = tryUnwrapObjcObject([_context JSGlobalContextRef], m_value))
81         return [wrapped description];
82     return [self toString];
83 }
84
85 - (JSValueRef)JSValueRef
86 {
87     return m_value;
88 }
89
90 + (JSValue *)valueWithObject:(id)value inContext:(JSContext *)context
91 {
92     return [JSValue valueWithJSValueRef:objectToValue(context, value) inContext:context];
93 }
94
95 + (JSValue *)valueWithBool:(BOOL)value inContext:(JSContext *)context
96 {
97     return [JSValue valueWithJSValueRef:JSValueMakeBoolean([context JSGlobalContextRef], value) inContext:context];
98 }
99
100 + (JSValue *)valueWithDouble:(double)value inContext:(JSContext *)context
101 {
102     return [JSValue valueWithJSValueRef:JSValueMakeNumber([context JSGlobalContextRef], value) inContext:context];
103 }
104
105 + (JSValue *)valueWithInt32:(int32_t)value inContext:(JSContext *)context
106 {
107     return [JSValue valueWithJSValueRef:JSValueMakeNumber([context JSGlobalContextRef], value) inContext:context];
108 }
109
110 + (JSValue *)valueWithUInt32:(uint32_t)value inContext:(JSContext *)context
111 {
112     return [JSValue valueWithJSValueRef:JSValueMakeNumber([context JSGlobalContextRef], value) inContext:context];
113 }
114
115 + (JSValue *)valueWithNewObjectInContext:(JSContext *)context
116 {
117     return [JSValue valueWithJSValueRef:JSObjectMake([context JSGlobalContextRef], 0, 0) inContext:context];
118 }
119
120 + (JSValue *)valueWithNewArrayInContext:(JSContext *)context
121 {
122     return [JSValue valueWithJSValueRef:JSObjectMakeArray([context JSGlobalContextRef], 0, NULL, 0) inContext:context];
123 }
124
125 + (JSValue *)valueWithNewRegularExpressionFromPattern:(NSString *)pattern flags:(NSString *)flags inContext:(JSContext *)context
126 {
127     JSStringRef patternString = JSStringCreateWithCFString((__bridge CFStringRef)pattern);
128     JSStringRef flagsString = JSStringCreateWithCFString((__bridge CFStringRef)flags);
129     JSValueRef arguments[2] = { JSValueMakeString([context JSGlobalContextRef], patternString), JSValueMakeString([context JSGlobalContextRef], flagsString) };
130     JSStringRelease(patternString);
131     JSStringRelease(flagsString);
132
133     return [JSValue valueWithJSValueRef:JSObjectMakeRegExp([context JSGlobalContextRef], 2, arguments, 0) inContext:context];
134 }
135
136 + (JSValue *)valueWithNewErrorFromMessage:(NSString *)message inContext:(JSContext *)context
137 {
138     JSStringRef string = JSStringCreateWithCFString((__bridge CFStringRef)message);
139     JSValueRef argument = JSValueMakeString([context JSGlobalContextRef], string);
140     JSStringRelease(string);
141
142     return [JSValue valueWithJSValueRef:JSObjectMakeError([context JSGlobalContextRef], 1, &argument, 0) inContext:context];
143 }
144
145 + (JSValue *)valueWithNullInContext:(JSContext *)context
146 {
147     return [JSValue valueWithJSValueRef:JSValueMakeNull([context JSGlobalContextRef]) inContext:context];
148 }
149
150 + (JSValue *)valueWithUndefinedInContext:(JSContext *)context
151 {
152     return [JSValue valueWithJSValueRef:JSValueMakeUndefined([context JSGlobalContextRef]) inContext:context];
153 }
154
155 + (JSValue *)valueWithNewSymbolFromDescription:(NSString *)description inContext:(JSContext *)context
156 {
157     JSStringRef string = JSStringCreateWithCFString(reinterpret_cast<CFStringRef>(description));
158     return [JSValue valueWithJSValueRef:JSValueMakeSymbol([context JSGlobalContextRef], string) inContext:context];
159 }
160
161
162 - (id)toObject
163 {
164     return valueToObject(_context, m_value);
165 }
166
167 - (id)toObjectOfClass:(Class)expectedClass
168 {
169     id result = [self toObject];
170     return [result isKindOfClass:expectedClass] ? result : nil;
171 }
172
173 - (BOOL)toBool
174 {
175     return JSValueToBoolean([_context JSGlobalContextRef], m_value);
176 }
177
178 - (double)toDouble
179 {
180     JSValueRef exception = 0;
181     double result = JSValueToNumber([_context JSGlobalContextRef], m_value, &exception);
182     if (exception) {
183         [_context notifyException:exception];
184         return std::numeric_limits<double>::quiet_NaN();
185     }
186
187     return result;
188 }
189
190 - (int32_t)toInt32
191 {
192     return JSC::toInt32([self toDouble]);
193 }
194
195 - (uint32_t)toUInt32
196 {
197     return JSC::toUInt32([self toDouble]);
198 }
199
200 - (NSNumber *)toNumber
201 {
202     JSValueRef exception = 0;
203     id result = valueToNumber([_context JSGlobalContextRef], m_value, &exception);
204     if (exception)
205         [_context notifyException:exception];
206     return result;
207 }
208
209 - (NSString *)toString
210 {
211     JSValueRef exception = 0;
212     id result = valueToString([_context JSGlobalContextRef], m_value, &exception);
213     if (exception)
214         [_context notifyException:exception];
215     return result;
216 }
217
218 - (NSDate *)toDate
219 {
220     JSValueRef exception = 0;
221     id result = valueToDate([_context JSGlobalContextRef], m_value, &exception);
222     if (exception)
223         [_context notifyException:exception];
224     return result;
225 }
226
227 - (NSArray *)toArray
228 {
229     JSValueRef exception = 0;
230     id result = valueToArray([_context JSGlobalContextRef], m_value, &exception);
231     if (exception)
232         [_context notifyException:exception];
233     return result;
234 }
235
236 - (NSDictionary *)toDictionary
237 {
238     JSValueRef exception = 0;
239     id result = valueToDictionary([_context JSGlobalContextRef], m_value, &exception);
240     if (exception)
241         [_context notifyException:exception];
242     return result;
243 }
244
245 template<typename Result, typename NSStringFunction, typename JSValueFunction, typename... Types>
246 inline Expected<Result, JSValueRef> performPropertyOperation(NSStringFunction stringFunction, JSValueFunction jsFunction, JSValue* value, id propertyKey, Types... arguments)
247 {
248     JSContext* context = [value context];
249     JSValueRef exception = nullptr;
250     JSObjectRef object = JSValueToObject([context JSGlobalContextRef], [value JSValueRef], &exception);
251     if (exception)
252         return Unexpected<JSValueRef>(exception);
253
254     Result result;
255     // If it's a NSString already, reduce indirection and just pass the NSString.
256     if ([propertyKey isKindOfClass:[NSString class]]) {
257         JSStringRef name = JSStringCreateWithCFString((__bridge CFStringRef)propertyKey);
258         result = stringFunction([context JSGlobalContextRef], object, name, arguments..., &exception);
259         JSStringRelease(name);
260     } else
261         result = jsFunction([context JSGlobalContextRef], object, [[JSValue valueWithObject:propertyKey inContext:context] JSValueRef], arguments..., &exception);
262     return Expected<Result, JSValueRef>(result);
263 }
264
265 - (JSValue *)valueForProperty:(id)key
266 {
267     auto result = performPropertyOperation<JSValueRef>(JSObjectGetProperty, JSObjectGetPropertyForKey, self, key);
268     if (!result)
269         return [_context valueFromNotifyException:result.error()];
270
271     return [JSValue valueWithJSValueRef:result.value() inContext:_context];
272 }
273
274
275 - (void)setValue:(id)value forProperty:(JSValueProperty)key
276 {
277     // We need Unit business because void can't be assigned to in performPropertyOperation and I don't want to duplicate the code...
278     using Unit = std::tuple<>;
279     auto stringSetProperty = [] (auto... args) -> Unit {
280         JSObjectSetProperty(args...);
281         return { };
282     };
283
284     auto jsValueSetProperty = [] (auto... args) -> Unit {
285         JSObjectSetPropertyForKey(args...);
286         return { };
287     };
288
289     auto result = performPropertyOperation<Unit>(stringSetProperty, jsValueSetProperty, self, key, objectToValue(_context, value), kJSPropertyAttributeNone);
290     if (!result) {
291         [_context notifyException:result.error()];
292         return;
293     }
294 }
295
296 - (BOOL)deleteProperty:(JSValueProperty)key
297 {
298     Expected<BOOL, JSValueRef> result = performPropertyOperation<BOOL>(JSObjectDeleteProperty, JSObjectDeletePropertyForKey, self, key);
299     if (!result)
300         return [_context boolFromNotifyException:result.error()];
301     return result.value();
302 }
303
304 - (BOOL)hasProperty:(JSValueProperty)key
305 {
306     // The C-api doesn't return an exception value for the string version of has property.
307     auto stringHasProperty = [] (JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef*) -> BOOL {
308         return JSObjectHasProperty(ctx, object, propertyName);
309     };
310
311     Expected<BOOL, JSValueRef> result = performPropertyOperation<BOOL>(stringHasProperty, JSObjectHasPropertyForKey, self, key);
312     if (!result)
313         return [_context boolFromNotifyException:result.error()];
314     return result.value();
315 }
316
317 - (void)defineProperty:(JSValueProperty)key descriptor:(id)descriptor
318 {
319     [[_context globalObject][@"Object"] invokeMethod:@"defineProperty" withArguments:@[ self, key, descriptor ]];
320 }
321
322 - (JSValue *)valueAtIndex:(NSUInteger)index
323 {
324     // Properties that are higher than an unsigned value can hold are converted to a double then inserted as a normal property.
325     // Indices that are bigger than the max allowed index size (UINT_MAX - 1) will be handled internally in get().
326     if (index != (unsigned)index)
327         return [self valueForProperty:[[JSValue valueWithDouble:index inContext:_context] toString]];
328
329     JSValueRef exception = 0;
330     JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
331     if (exception)
332         return [_context valueFromNotifyException:exception];
333
334     JSValueRef result = JSObjectGetPropertyAtIndex([_context JSGlobalContextRef], object, (unsigned)index, &exception);
335     if (exception)
336         return [_context valueFromNotifyException:exception];
337
338     return [JSValue valueWithJSValueRef:result inContext:_context];
339 }
340
341 - (void)setValue:(id)value atIndex:(NSUInteger)index
342 {
343     // Properties that are higher than an unsigned value can hold are converted to a double, then inserted as a normal property.
344     // Indices that are bigger than the max allowed index size (UINT_MAX - 1) will be handled internally in putByIndex().
345     if (index != (unsigned)index)
346         return [self setValue:value forProperty:[[JSValue valueWithDouble:index inContext:_context] toString]];
347
348     JSValueRef exception = 0;
349     JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
350     if (exception) {
351         [_context notifyException:exception];
352         return;
353     }
354
355     JSObjectSetPropertyAtIndex([_context JSGlobalContextRef], object, (unsigned)index, objectToValue(_context, value), &exception);
356     if (exception) {
357         [_context notifyException:exception];
358         return;
359     }
360 }
361
362 - (BOOL)isUndefined
363 {
364     return JSValueIsUndefined([_context JSGlobalContextRef], m_value);
365 }
366
367 - (BOOL)isNull
368 {
369     return JSValueIsNull([_context JSGlobalContextRef], m_value);
370 }
371
372 - (BOOL)isBoolean
373 {
374     return JSValueIsBoolean([_context JSGlobalContextRef], m_value);
375 }
376
377 - (BOOL)isNumber
378 {
379     return JSValueIsNumber([_context JSGlobalContextRef], m_value);
380 }
381
382 - (BOOL)isString
383 {
384     return JSValueIsString([_context JSGlobalContextRef], m_value);
385 }
386
387 - (BOOL)isObject
388 {
389     return JSValueIsObject([_context JSGlobalContextRef], m_value);
390 }
391
392 - (BOOL)isSymbol
393 {
394     return JSValueIsSymbol([_context JSGlobalContextRef], m_value);
395 }
396
397 - (BOOL)isArray
398 {
399     return JSValueIsArray([_context JSGlobalContextRef], m_value);
400 }
401
402 - (BOOL)isDate
403 {
404     return JSValueIsDate([_context JSGlobalContextRef], m_value);
405 }
406
407 - (BOOL)isEqualToObject:(id)value
408 {
409     return JSValueIsStrictEqual([_context JSGlobalContextRef], m_value, objectToValue(_context, value));
410 }
411
412 - (BOOL)isEqualWithTypeCoercionToObject:(id)value
413 {
414     JSValueRef exception = 0;
415     BOOL result = JSValueIsEqual([_context JSGlobalContextRef], m_value, objectToValue(_context, value), &exception);
416     if (exception)
417         return [_context boolFromNotifyException:exception];
418
419     return result;
420 }
421
422 - (BOOL)isInstanceOf:(id)value
423 {
424     JSValueRef exception = 0;
425     JSObjectRef constructor = JSValueToObject([_context JSGlobalContextRef], objectToValue(_context, value), &exception);
426     if (exception)
427         return [_context boolFromNotifyException:exception];
428
429     BOOL result = JSValueIsInstanceOfConstructor([_context JSGlobalContextRef], m_value, constructor, &exception);
430     if (exception)
431         return [_context boolFromNotifyException:exception];
432
433     return result;
434 }
435
436 - (JSValue *)callWithArguments:(NSArray *)argumentArray
437 {
438     NSUInteger argumentCount = [argumentArray count];
439     JSValueRef arguments[argumentCount];
440     for (unsigned i = 0; i < argumentCount; ++i)
441         arguments[i] = objectToValue(_context, [argumentArray objectAtIndex:i]);
442
443     JSValueRef exception = 0;
444     JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
445     if (exception)
446         return [_context valueFromNotifyException:exception];
447
448     JSValueRef result = JSObjectCallAsFunction([_context JSGlobalContextRef], object, 0, argumentCount, arguments, &exception);
449     if (exception)
450         return [_context valueFromNotifyException:exception];
451
452     return [JSValue valueWithJSValueRef:result inContext:_context];
453 }
454
455 - (JSValue *)constructWithArguments:(NSArray *)argumentArray
456 {
457     NSUInteger argumentCount = [argumentArray count];
458     JSValueRef arguments[argumentCount];
459     for (unsigned i = 0; i < argumentCount; ++i)
460         arguments[i] = objectToValue(_context, [argumentArray objectAtIndex:i]);
461
462     JSValueRef exception = 0;
463     JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
464     if (exception)
465         return [_context valueFromNotifyException:exception];
466
467     JSObjectRef result = JSObjectCallAsConstructor([_context JSGlobalContextRef], object, argumentCount, arguments, &exception);
468     if (exception)
469         return [_context valueFromNotifyException:exception];
470
471     return [JSValue valueWithJSValueRef:result inContext:_context];
472 }
473
474 - (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments
475 {
476     NSUInteger argumentCount = [arguments count];
477     JSValueRef argumentArray[argumentCount];
478     for (unsigned i = 0; i < argumentCount; ++i)
479         argumentArray[i] = objectToValue(_context, [arguments objectAtIndex:i]);
480
481     JSValueRef exception = 0;
482     JSObjectRef thisObject = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
483     if (exception)
484         return [_context valueFromNotifyException:exception];
485
486     JSStringRef name = JSStringCreateWithCFString((__bridge CFStringRef)method);
487     JSValueRef function = JSObjectGetProperty([_context JSGlobalContextRef], thisObject, name, &exception);
488     JSStringRelease(name);
489     if (exception)
490         return [_context valueFromNotifyException:exception];
491
492     JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], function, &exception);
493     if (exception)
494         return [_context valueFromNotifyException:exception];
495
496     JSValueRef result = JSObjectCallAsFunction([_context JSGlobalContextRef], object, thisObject, argumentCount, argumentArray, &exception);
497     if (exception)
498         return [_context valueFromNotifyException:exception];
499
500     return [JSValue valueWithJSValueRef:result inContext:_context];
501 }
502
503 @end
504
505 @implementation JSValue(StructSupport)
506
507 - (CGPoint)toPoint
508 {
509     return (CGPoint){
510         static_cast<CGFloat>([self[@"x"] toDouble]),
511         static_cast<CGFloat>([self[@"y"] toDouble])
512     };
513 }
514
515 - (NSRange)toRange
516 {
517     return (NSRange){
518         [[self[@"location"] toNumber] unsignedIntegerValue],
519         [[self[@"length"] toNumber] unsignedIntegerValue]
520     };
521 }
522
523 - (CGRect)toRect
524 {
525     return (CGRect){
526         [self toPoint],
527         [self toSize]
528     };
529 }
530
531 - (CGSize)toSize
532 {
533     return (CGSize){
534         static_cast<CGFloat>([self[@"width"] toDouble]),
535         static_cast<CGFloat>([self[@"height"] toDouble])
536     };
537 }
538
539 + (JSValue *)valueWithPoint:(CGPoint)point inContext:(JSContext *)context
540 {
541     return [JSValue valueWithObject:@{
542         @"x":@(point.x),
543         @"y":@(point.y)
544     } inContext:context];
545 }
546
547 + (JSValue *)valueWithRange:(NSRange)range inContext:(JSContext *)context
548 {
549     return [JSValue valueWithObject:@{
550         @"location":@(range.location),
551         @"length":@(range.length)
552     } inContext:context];
553 }
554
555 + (JSValue *)valueWithRect:(CGRect)rect inContext:(JSContext *)context
556 {
557     return [JSValue valueWithObject:@{
558         @"x":@(rect.origin.x),
559         @"y":@(rect.origin.y),
560         @"width":@(rect.size.width),
561         @"height":@(rect.size.height)
562     } inContext:context];
563 }
564
565 + (JSValue *)valueWithSize:(CGSize)size inContext:(JSContext *)context
566 {
567     return [JSValue valueWithObject:@{
568         @"width":@(size.width),
569         @"height":@(size.height)
570     } inContext:context];
571 }
572
573 @end
574
575 @implementation JSValue(SubscriptSupport)
576
577 - (JSValue *)objectForKeyedSubscript:(id)key
578 {
579     return [self valueForProperty:key];
580 }
581
582 - (JSValue *)objectAtIndexedSubscript:(NSUInteger)index
583 {
584     return [self valueAtIndex:index];
585 }
586
587 - (void)setObject:(id)object forKeyedSubscript:(id)key
588 {
589     [self setValue:object forProperty:key];
590 }
591
592 - (void)setObject:(id)object atIndexedSubscript:(NSUInteger)index
593 {
594     [self setValue:object atIndex:index];
595 }
596
597 @end
598
599 inline bool isDate(JSC::VM& vm, JSObjectRef object, JSGlobalContextRef context)
600 {
601     JSC::JSLockHolder locker(toJS(context));
602     return toJS(object)->inherits<JSC::DateInstance>(vm);
603 }
604
605 inline bool isArray(JSC::VM& vm, JSObjectRef object, JSGlobalContextRef context)
606 {
607     JSC::JSLockHolder locker(toJS(context));
608     return toJS(object)->inherits<JSC::JSArray>(vm);
609 }
610
611 @implementation JSValue(Internal)
612
613 enum ConversionType {
614     ContainerNone,
615     ContainerArray,
616     ContainerDictionary
617 };
618
619 class JSContainerConvertor {
620 public:
621     struct Task {
622         JSValueRef js;
623         id objc;
624         ConversionType type;
625     };
626
627     JSContainerConvertor(JSGlobalContextRef context)
628         : m_context(context)
629     {
630     }
631
632     id convert(JSValueRef property);
633     void add(Task);
634     Task take();
635     bool isWorkListEmpty() const { return !m_worklist.size(); }
636
637 private:
638     JSGlobalContextRef m_context;
639     HashMap<JSValueRef, __unsafe_unretained id> m_objectMap;
640     Vector<Task> m_worklist;
641     Vector<JSC::Strong<JSC::Unknown>> m_jsValues;
642 };
643
644 inline id JSContainerConvertor::convert(JSValueRef value)
645 {
646     auto iter = m_objectMap.find(value);
647     if (iter != m_objectMap.end())
648         return iter->value;
649
650     Task result = valueToObjectWithoutCopy(m_context, value);
651     if (result.js)
652         add(result);
653     return result.objc;
654 }
655
656 void JSContainerConvertor::add(Task task)
657 {
658     JSC::ExecState* exec = toJS(m_context);
659     m_jsValues.append(JSC::Strong<JSC::Unknown>(exec->vm(), toJSForGC(exec, task.js)));
660     m_objectMap.add(task.js, task.objc);
661     if (task.type != ContainerNone)
662         m_worklist.append(task);
663 }
664
665 JSContainerConvertor::Task JSContainerConvertor::take()
666 {
667     ASSERT(!isWorkListEmpty());
668     Task last = m_worklist.last();
669     m_worklist.removeLast();
670     return last;
671 }
672
673 #if ENABLE(REMOTE_INSPECTOR)
674 static void reportExceptionToInspector(JSGlobalContextRef context, JSC::JSValue exceptionValue)
675 {
676     JSC::ExecState* exec = toJS(context);
677     JSC::VM& vm = exec->vm();
678     JSC::Exception* exception = JSC::Exception::create(vm, exceptionValue);
679     vm.vmEntryGlobalObject(exec)->inspectorController().reportAPIException(exec, exception);
680 }
681 #endif
682
683 static JSContainerConvertor::Task valueToObjectWithoutCopy(JSGlobalContextRef context, JSValueRef value)
684 {
685     JSC::ExecState* exec = toJS(context);
686     JSC::VM& vm = exec->vm();
687
688     if (!JSValueIsObject(context, value)) {
689         id primitive;
690         if (JSValueIsBoolean(context, value))
691             primitive = JSValueToBoolean(context, value) ? @YES : @NO;
692         else if (JSValueIsNumber(context, value)) {
693             // Normalize the number, so it will unique correctly in the hash map -
694             // it's nicer not to leak this internal implementation detail!
695             value = JSValueMakeNumber(context, JSValueToNumber(context, value, 0));
696             primitive = [NSNumber numberWithDouble:JSValueToNumber(context, value, 0)];
697         } else if (JSValueIsString(context, value)) {
698             // Would be nice to unique strings, too.
699             JSStringRef jsstring = JSValueToStringCopy(context, value, 0);
700             primitive = CFBridgingRelease(JSStringCopyCFString(kCFAllocatorDefault, jsstring));
701             JSStringRelease(jsstring);
702         } else if (JSValueIsNull(context, value))
703             primitive = [NSNull null];
704         else {
705             ASSERT(JSValueIsUndefined(context, value));
706             primitive = nil;
707         }
708         return { value, primitive, ContainerNone };
709     }
710
711     JSObjectRef object = JSValueToObject(context, value, 0);
712
713     if (id wrapped = tryUnwrapObjcObject(context, object))
714         return { object, wrapped, ContainerNone };
715
716     if (isDate(vm, object, context))
717         return { object, [NSDate dateWithTimeIntervalSince1970:JSValueToNumber(context, object, 0) / 1000.0], ContainerNone };
718
719     if (isArray(vm, object, context))
720         return { object, [NSMutableArray array], ContainerArray };
721
722     return { object, [NSMutableDictionary dictionary], ContainerDictionary };
723 }
724
725 static id containerValueToObject(JSGlobalContextRef context, JSContainerConvertor::Task task)
726 {
727     ASSERT(task.type != ContainerNone);
728     JSC::JSLockHolder locker(toJS(context));
729     JSContainerConvertor convertor(context);
730     convertor.add(task);
731     ASSERT(!convertor.isWorkListEmpty());
732     
733     do {
734         JSContainerConvertor::Task current = convertor.take();
735         ASSERT(JSValueIsObject(context, current.js));
736         JSObjectRef js = JSValueToObject(context, current.js, 0);
737
738         if (current.type == ContainerArray) {
739             ASSERT([current.objc isKindOfClass:[NSMutableArray class]]);
740             NSMutableArray *array = (NSMutableArray *)current.objc;
741         
742             JSStringRef lengthString = JSStringCreateWithUTF8CString("length");
743             unsigned length = JSC::toUInt32(JSValueToNumber(context, JSObjectGetProperty(context, js, lengthString, 0), 0));
744             JSStringRelease(lengthString);
745
746             for (unsigned i = 0; i < length; ++i) {
747                 id objc = convertor.convert(JSObjectGetPropertyAtIndex(context, js, i, 0));
748                 [array addObject:objc ? objc : [NSNull null]];
749             }
750         } else {
751             ASSERT([current.objc isKindOfClass:[NSMutableDictionary class]]);
752             NSMutableDictionary *dictionary = (NSMutableDictionary *)current.objc;
753
754             JSC::JSLockHolder locker(toJS(context));
755
756             JSPropertyNameArrayRef propertyNameArray = JSObjectCopyPropertyNames(context, js);
757             size_t length = JSPropertyNameArrayGetCount(propertyNameArray);
758
759             for (size_t i = 0; i < length; ++i) {
760                 JSStringRef propertyName = JSPropertyNameArrayGetNameAtIndex(propertyNameArray, i);
761                 if (id objc = convertor.convert(JSObjectGetProperty(context, js, propertyName, 0)))
762                     dictionary[(__bridge NSString *)adoptCF(JSStringCopyCFString(kCFAllocatorDefault, propertyName)).get()] = objc;
763             }
764
765             JSPropertyNameArrayRelease(propertyNameArray);
766         }
767
768     } while (!convertor.isWorkListEmpty());
769
770     return task.objc;
771 }
772
773 id valueToObject(JSContext *context, JSValueRef value)
774 {
775     JSContainerConvertor::Task result = valueToObjectWithoutCopy([context JSGlobalContextRef], value);
776     if (result.type == ContainerNone)
777         return result.objc;
778     return containerValueToObject([context JSGlobalContextRef], result);
779 }
780
781 id valueToNumber(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
782 {
783     ASSERT(!*exception);
784     if (id wrapped = tryUnwrapObjcObject(context, value)) {
785         if ([wrapped isKindOfClass:[NSNumber class]])
786             return wrapped;
787     }
788
789     if (JSValueIsBoolean(context, value))
790         return JSValueToBoolean(context, value) ? @YES : @NO;
791
792     double result = JSValueToNumber(context, value, exception);
793     return [NSNumber numberWithDouble:*exception ? std::numeric_limits<double>::quiet_NaN() : result];
794 }
795
796 id valueToString(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
797 {
798     ASSERT(!*exception);
799     if (id wrapped = tryUnwrapObjcObject(context, value)) {
800         if ([wrapped isKindOfClass:[NSString class]])
801             return wrapped;
802     }
803
804     JSStringRef jsstring = JSValueToStringCopy(context, value, exception);
805     if (*exception) {
806         ASSERT(!jsstring);
807         return nil;
808     }
809
810     NSString *string = CFBridgingRelease(JSStringCopyCFString(kCFAllocatorDefault, jsstring));
811     JSStringRelease(jsstring);
812     return string;
813 }
814
815 id valueToDate(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
816 {
817     ASSERT(!*exception);
818     if (id wrapped = tryUnwrapObjcObject(context, value)) {
819         if ([wrapped isKindOfClass:[NSDate class]])
820             return wrapped;
821     }
822
823     double result = JSValueToNumber(context, value, exception) / 1000.0;
824     return *exception ? nil : [NSDate dateWithTimeIntervalSince1970:result];
825 }
826
827 id valueToArray(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
828 {
829     ASSERT(!*exception);
830     if (id wrapped = tryUnwrapObjcObject(context, value)) {
831         if ([wrapped isKindOfClass:[NSArray class]])
832             return wrapped;
833     }
834
835     if (JSValueIsObject(context, value))
836         return containerValueToObject(context, { value, [NSMutableArray array], ContainerArray});
837
838     JSC::JSLockHolder locker(toJS(context));
839     if (!(JSValueIsNull(context, value) || JSValueIsUndefined(context, value))) {
840         JSC::JSObject* exceptionObject = JSC::createTypeError(toJS(context), "Cannot convert primitive to NSArray"_s);
841         *exception = toRef(exceptionObject);
842 #if ENABLE(REMOTE_INSPECTOR)
843         reportExceptionToInspector(context, exceptionObject);
844 #endif
845     }
846     return nil;
847 }
848
849 id valueToDictionary(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
850 {
851     ASSERT(!*exception);
852     if (id wrapped = tryUnwrapObjcObject(context, value)) {
853         if ([wrapped isKindOfClass:[NSDictionary class]])
854             return wrapped;
855     }
856
857     if (JSValueIsObject(context, value))
858         return containerValueToObject(context, { value, [NSMutableDictionary dictionary], ContainerDictionary});
859
860     JSC::JSLockHolder locker(toJS(context));
861     if (!(JSValueIsNull(context, value) || JSValueIsUndefined(context, value))) {
862         JSC::JSObject* exceptionObject = JSC::createTypeError(toJS(context), "Cannot convert primitive to NSDictionary"_s);
863         *exception = toRef(exceptionObject);
864 #if ENABLE(REMOTE_INSPECTOR)
865         reportExceptionToInspector(context, exceptionObject);
866 #endif
867     }
868     return nil;
869 }
870
871 class ObjcContainerConvertor {
872 public:
873     struct Task {
874         id objc;
875         JSValueRef js;
876         ConversionType type;
877     };
878
879     ObjcContainerConvertor(JSContext *context)
880         : m_context(context)
881     {
882     }
883
884     JSValueRef convert(id object);
885     void add(Task);
886     Task take();
887     bool isWorkListEmpty() const { return !m_worklist.size(); }
888
889 private:
890     JSContext *m_context;
891     HashMap<__unsafe_unretained id, JSValueRef> m_objectMap;
892     Vector<Task> m_worklist;
893     Vector<JSC::Strong<JSC::Unknown>> m_jsValues;
894 };
895
896 JSValueRef ObjcContainerConvertor::convert(id object)
897 {
898     ASSERT(object);
899
900     auto it = m_objectMap.find(object);
901     if (it != m_objectMap.end())
902         return it->value;
903
904     ObjcContainerConvertor::Task task = objectToValueWithoutCopy(m_context, object);
905     add(task);
906     return task.js;
907 }
908
909 void ObjcContainerConvertor::add(ObjcContainerConvertor::Task task)
910 {
911     JSC::ExecState* exec = toJS(m_context.JSGlobalContextRef);
912     m_jsValues.append(JSC::Strong<JSC::Unknown>(exec->vm(), toJSForGC(exec, task.js)));
913     m_objectMap.add(task.objc, task.js);
914     if (task.type != ContainerNone)
915         m_worklist.append(task);
916 }
917
918 ObjcContainerConvertor::Task ObjcContainerConvertor::take()
919 {
920     ASSERT(!isWorkListEmpty());
921     Task last = m_worklist.last();
922     m_worklist.removeLast();
923     return last;
924 }
925
926 inline bool isNSBoolean(id object)
927 {
928     ASSERT([@YES class] == [@NO class]);
929     ASSERT([@YES class] != [NSNumber class]);
930     ASSERT([[@YES class] isSubclassOfClass:[NSNumber class]]);
931     return [object isKindOfClass:[@YES class]];
932 }
933
934 static ObjcContainerConvertor::Task objectToValueWithoutCopy(JSContext *context, id object)
935 {
936     JSGlobalContextRef contextRef = [context JSGlobalContextRef];
937
938     if (!object)
939         return { object, JSValueMakeUndefined(contextRef), ContainerNone };
940
941     if (!class_conformsToProtocol(object_getClass(object), getJSExportProtocol())) {
942         if ([object isKindOfClass:[NSArray class]])
943             return { object, JSObjectMakeArray(contextRef, 0, NULL, 0), ContainerArray };
944
945         if ([object isKindOfClass:[NSDictionary class]])
946             return { object, JSObjectMake(contextRef, 0, 0), ContainerDictionary };
947
948         if ([object isKindOfClass:[NSNull class]])
949             return { object, JSValueMakeNull(contextRef), ContainerNone };
950
951         if ([object isKindOfClass:[JSValue class]])
952             return { object, ((JSValue *)object)->m_value, ContainerNone };
953
954         if ([object isKindOfClass:[NSString class]]) {
955             JSStringRef string = JSStringCreateWithCFString((__bridge CFStringRef)object);
956             JSValueRef js = JSValueMakeString(contextRef, string);
957             JSStringRelease(string);
958             return { object, js, ContainerNone };
959         }
960
961         if ([object isKindOfClass:[NSNumber class]]) {
962             if (isNSBoolean(object))
963                 return { object, JSValueMakeBoolean(contextRef, [object boolValue]), ContainerNone };
964             return { object, JSValueMakeNumber(contextRef, [object doubleValue]), ContainerNone };
965         }
966
967         if ([object isKindOfClass:[NSDate class]]) {
968             JSValueRef argument = JSValueMakeNumber(contextRef, [object timeIntervalSince1970] * 1000.0);
969             JSObjectRef result = JSObjectMakeDate(contextRef, 1, &argument, 0);
970             return { object, result, ContainerNone };
971         }
972
973         if ([object isKindOfClass:[JSManagedValue class]]) {
974             JSValue *value = [static_cast<JSManagedValue *>(object) value];
975             if (!value)
976                 return  { object, JSValueMakeUndefined(contextRef), ContainerNone };
977             return { object, value->m_value, ContainerNone };
978         }
979     }
980
981     return { object, valueInternalValue([context wrapperForObjCObject:object]), ContainerNone };
982 }
983
984 JSValueRef objectToValue(JSContext *context, id object)
985 {
986     JSGlobalContextRef contextRef = [context JSGlobalContextRef];
987
988     ObjcContainerConvertor::Task task = objectToValueWithoutCopy(context, object);
989     if (task.type == ContainerNone)
990         return task.js;
991
992     JSC::JSLockHolder locker(toJS(contextRef));
993     ObjcContainerConvertor convertor(context);
994     convertor.add(task);
995     ASSERT(!convertor.isWorkListEmpty());
996
997     do {
998         ObjcContainerConvertor::Task current = convertor.take();
999         ASSERT(JSValueIsObject(contextRef, current.js));
1000         JSObjectRef js = JSValueToObject(contextRef, current.js, 0);
1001
1002         if (current.type == ContainerArray) {
1003             ASSERT([current.objc isKindOfClass:[NSArray class]]);
1004             NSArray *array = (NSArray *)current.objc;
1005             NSUInteger count = [array count];
1006             for (NSUInteger index = 0; index < count; ++index)
1007                 JSObjectSetPropertyAtIndex(contextRef, js, index, convertor.convert([array objectAtIndex:index]), 0);
1008         } else {
1009             ASSERT(current.type == ContainerDictionary);
1010             ASSERT([current.objc isKindOfClass:[NSDictionary class]]);
1011             NSDictionary *dictionary = (NSDictionary *)current.objc;
1012             for (id key in [dictionary keyEnumerator]) {
1013                 if ([key isKindOfClass:[NSString class]]) {
1014                     JSStringRef propertyName = JSStringCreateWithCFString((__bridge CFStringRef)key);
1015                     JSObjectSetProperty(contextRef, js, propertyName, convertor.convert([dictionary objectForKey:key]), 0, 0);
1016                     JSStringRelease(propertyName);
1017                 }
1018             }
1019         }
1020         
1021     } while (!convertor.isWorkListEmpty());
1022
1023     return task.js;
1024 }
1025
1026 JSValueRef valueInternalValue(JSValue * value)
1027 {
1028     return value->m_value;
1029 }
1030
1031 + (JSValue *)valueWithJSValueRef:(JSValueRef)value inContext:(JSContext *)context
1032 {
1033     return [context wrapperForJSObject:value];
1034 }
1035
1036 - (JSValue *)init
1037 {
1038     return nil;
1039 }
1040
1041 - (JSValue *)initWithValue:(JSValueRef)value inContext:(JSContext *)context
1042 {
1043     if (!value || !context)
1044         return nil;
1045
1046     self = [super init];
1047     if (!self)
1048         return nil;
1049
1050     _context = [context retain];
1051     m_value = value;
1052     JSValueProtect([_context JSGlobalContextRef], m_value);
1053     return self;
1054 }
1055
1056 struct StructTagHandler {
1057     SEL typeToValueSEL;
1058     SEL valueToTypeSEL;
1059 };
1060 typedef HashMap<String, StructTagHandler> StructHandlers;
1061
1062 static StructHandlers* createStructHandlerMap()
1063 {
1064     StructHandlers* structHandlers = new StructHandlers();
1065
1066     size_t valueWithXinContextLength = strlen("valueWithX:inContext:");
1067     size_t toXLength = strlen("toX");
1068
1069     // Step 1: find all valueWith<Foo>:inContext: class methods in JSValue.
1070     forEachMethodInClass(object_getClass([JSValue class]), ^(Method method){
1071         SEL selector = method_getName(method);
1072         const char* name = sel_getName(selector);
1073         size_t nameLength = strlen(name);
1074         // Check for valueWith<Foo>:context:
1075         if (nameLength < valueWithXinContextLength || memcmp(name, "valueWith", 9) || memcmp(name + nameLength - 11, ":inContext:", 11))
1076             return;
1077         // Check for [ id, SEL, <type>, <contextType> ]
1078         if (method_getNumberOfArguments(method) != 4)
1079             return;
1080         char idType[3];
1081         // Check 2nd argument type is "@"
1082         {
1083             auto secondType = adoptSystem<char[]>(method_copyArgumentType(method, 3));
1084             if (strcmp(secondType.get(), "@") != 0)
1085                 return;
1086         }
1087         // Check result type is also "@"
1088         method_getReturnType(method, idType, 3);
1089         if (strcmp(idType, "@") != 0)
1090             return;
1091         {
1092             auto type = adoptSystem<char[]>(method_copyArgumentType(method, 2));
1093             structHandlers->add(StringImpl::create(type.get()), (StructTagHandler) { selector, 0 });
1094         }
1095     });
1096
1097     // Step 2: find all to<Foo> instance methods in JSValue.
1098     forEachMethodInClass([JSValue class], ^(Method method){
1099         SEL selector = method_getName(method);
1100         const char* name = sel_getName(selector);
1101         size_t nameLength = strlen(name);
1102         // Check for to<Foo>
1103         if (nameLength < toXLength || memcmp(name, "to", 2))
1104             return;
1105         // Check for [ id, SEL ]
1106         if (method_getNumberOfArguments(method) != 2)
1107             return;
1108         // Try to find a matching valueWith<Foo>:context: method.
1109         auto type = adoptSystem<char[]>(method_copyReturnType(method));
1110         StructHandlers::iterator iter = structHandlers->find(type.get());
1111         if (iter == structHandlers->end())
1112             return;
1113         StructTagHandler& handler = iter->value;
1114
1115         // check that strlen(<foo>) == strlen(<Foo>)
1116         const char* valueWithName = sel_getName(handler.typeToValueSEL);
1117         size_t valueWithLength = strlen(valueWithName);
1118         if (valueWithLength - valueWithXinContextLength != nameLength - toXLength)
1119             return;
1120         // Check that <Foo> == <Foo>
1121         if (memcmp(valueWithName + 9, name + 2, nameLength - toXLength - 1))
1122             return;
1123         handler.valueToTypeSEL = selector;
1124     });
1125
1126     // Step 3: clean up - remove entries where we found prospective valueWith<Foo>:inContext: conversions, but no matching to<Foo> methods.
1127     typedef HashSet<String> RemoveSet;
1128     RemoveSet removeSet;
1129     for (StructHandlers::iterator iter = structHandlers->begin(); iter != structHandlers->end(); ++iter) {
1130         StructTagHandler& handler = iter->value;
1131         if (!handler.valueToTypeSEL)
1132             removeSet.add(iter->key);
1133     }
1134
1135     for (RemoveSet::iterator iter = removeSet.begin(); iter != removeSet.end(); ++iter)
1136         structHandlers->remove(*iter);
1137
1138     return structHandlers;
1139 }
1140
1141 static StructTagHandler* handerForStructTag(const char* encodedType)
1142 {
1143     static Lock handerForStructTagLock;
1144     LockHolder lockHolder(&handerForStructTagLock);
1145
1146     static StructHandlers* structHandlers = createStructHandlerMap();
1147
1148     StructHandlers::iterator iter = structHandlers->find(encodedType);
1149     if (iter == structHandlers->end())
1150         return 0;
1151     return &iter->value;
1152 }
1153
1154 + (SEL)selectorForStructToValue:(const char *)structTag
1155 {
1156     StructTagHandler* handler = handerForStructTag(structTag);
1157     return handler ? handler->typeToValueSEL : nil;
1158 }
1159
1160 + (SEL)selectorForValueToStruct:(const char *)structTag
1161 {
1162     StructTagHandler* handler = handerForStructTag(structTag);
1163     return handler ? handler->valueToTypeSEL : nil;
1164 }
1165
1166 NSInvocation *typeToValueInvocationFor(const char* encodedType)
1167 {
1168     SEL selector = [JSValue selectorForStructToValue:encodedType];
1169     if (!selector)
1170         return 0;
1171
1172     const char* methodTypes = method_getTypeEncoding(class_getClassMethod([JSValue class], selector));
1173     NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:methodTypes]];
1174     [invocation setSelector:selector];
1175     return invocation;
1176 }
1177
1178 NSInvocation *valueToTypeInvocationFor(const char* encodedType)
1179 {
1180     SEL selector = [JSValue selectorForValueToStruct:encodedType];
1181     if (!selector)
1182         return 0;
1183
1184     const char* methodTypes = method_getTypeEncoding(class_getInstanceMethod([JSValue class], selector));
1185     NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:methodTypes]];
1186     [invocation setSelector:selector];
1187     return invocation;
1188 }
1189
1190 @end
1191
1192 #endif