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