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