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