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