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