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