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