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