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