2  * Copyright (C) 2013 Apple Inc. All rights reserved.
 
   4  * Redistribution and use in source and binary forms, with or without
 
   5  * modification, are permitted provided that the following conditions
 
   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.
 
  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. 
 
  30 #import "DateInstance.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>
 
  47 #if JSC_OBJC_API_ENABLED
 
  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";
 
  56 @implementation JSValue {
 
  60 - (JSValueRef)JSValueRef
 
  65 + (JSValue *)valueWithObject:(id)value inContext:(JSContext *)context
 
  67     return [JSValue valueWithJSValueRef:objectToValue(context, value) inContext:context];
 
  70 + (JSValue *)valueWithBool:(BOOL)value inContext:(JSContext *)context
 
  72     return [JSValue valueWithJSValueRef:JSValueMakeBoolean([context JSGlobalContextRef], value) inContext:context];
 
  75 + (JSValue *)valueWithDouble:(double)value inContext:(JSContext *)context
 
  77     return [JSValue valueWithJSValueRef:JSValueMakeNumber([context JSGlobalContextRef], value) inContext:context];
 
  80 + (JSValue *)valueWithInt32:(int32_t)value inContext:(JSContext *)context
 
  82     return [JSValue valueWithJSValueRef:JSValueMakeNumber([context JSGlobalContextRef], value) inContext:context];
 
  85 + (JSValue *)valueWithUInt32:(uint32_t)value inContext:(JSContext *)context
 
  87     return [JSValue valueWithJSValueRef:JSValueMakeNumber([context JSGlobalContextRef], value) inContext:context];
 
  90 + (JSValue *)valueWithNewObjectInContext:(JSContext *)context
 
  92     return [JSValue valueWithJSValueRef:JSObjectMake([context JSGlobalContextRef], 0, 0) inContext:context];
 
  95 + (JSValue *)valueWithNewArrayInContext:(JSContext *)context
 
  97     return [JSValue valueWithJSValueRef:JSObjectMakeArray([context JSGlobalContextRef], 0, NULL, 0) inContext:context];
 
 100 + (JSValue *)valueWithNewRegularExpressionFromPattern:(NSString *)pattern flags:(NSString *)flags inContext:(JSContext *)context
 
 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);
 
 108     return [JSValue valueWithJSValueRef:JSObjectMakeRegExp([context JSGlobalContextRef], 2, arguments, 0) inContext:context];
 
 111 + (JSValue *)valueWithNewErrorFromMessage:(NSString *)message inContext:(JSContext *)context
 
 113     JSStringRef string = JSStringCreateWithCFString((CFStringRef)message);
 
 114     JSValueRef argument = JSValueMakeString([context JSGlobalContextRef], string);
 
 115     JSStringRelease(string);
 
 117     return [JSValue valueWithJSValueRef:JSObjectMakeError([context JSGlobalContextRef], 1, &argument, 0) inContext:context];
 
 120 + (JSValue *)valueWithNullInContext:(JSContext *)context
 
 122     return [JSValue valueWithJSValueRef:JSValueMakeNull([context JSGlobalContextRef]) inContext:context];
 
 125 + (JSValue *)valueWithUndefinedInContext:(JSContext *)context
 
 127     return [JSValue valueWithJSValueRef:JSValueMakeUndefined([context JSGlobalContextRef]) inContext:context];
 
 132     return valueToObject(_context, m_value);
 
 135 - (id)toObjectOfClass:(Class)expectedClass
 
 137     id result = [self toObject];
 
 138     return [result isKindOfClass:expectedClass] ? result : nil;
 
 143     return JSValueToBoolean([_context JSGlobalContextRef], m_value);
 
 148     JSValueRef exception = 0;
 
 149     double result = JSValueToNumber([_context JSGlobalContextRef], m_value, &exception);
 
 151         [_context notifyException:exception];
 
 152         return std::numeric_limits<double>::quiet_NaN();
 
 160     return JSC::toInt32([self toDouble]);
 
 165     return JSC::toUInt32([self toDouble]);
 
 168 - (NSNumber *)toNumber
 
 170     JSValueRef exception = 0;
 
 171     id result = valueToNumber([_context JSGlobalContextRef], m_value, &exception);
 
 173         [_context notifyException:exception];
 
 177 - (NSString *)toString
 
 179     JSValueRef exception = 0;
 
 180     id result = valueToString([_context JSGlobalContextRef], m_value, &exception);
 
 182         [_context notifyException:exception];
 
 188     JSValueRef exception = 0;
 
 189     id result = valueToDate([_context JSGlobalContextRef], m_value, &exception);
 
 191         [_context notifyException:exception];
 
 197     JSValueRef exception = 0;
 
 198     id result = valueToArray([_context JSGlobalContextRef], m_value, &exception);
 
 200         [_context notifyException:exception];
 
 204 - (NSDictionary *)toDictionary
 
 206     JSValueRef exception = 0;
 
 207     id result = valueToDictionary([_context JSGlobalContextRef], m_value, &exception);
 
 209         [_context notifyException:exception];
 
 213 - (JSValue *)valueForProperty:(NSString *)propertyName
 
 215     JSValueRef exception = 0;
 
 216     JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
 
 218         return [_context valueFromNotifyException:exception];
 
 220     JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName);
 
 221     JSValueRef result = JSObjectGetProperty([_context JSGlobalContextRef], object, name, &exception);
 
 222     JSStringRelease(name);
 
 224         return [_context valueFromNotifyException:exception];
 
 226     return [JSValue valueWithJSValueRef:result inContext:_context];
 
 229 - (void)setValue:(id)value forProperty:(NSString *)propertyName
 
 231     JSValueRef exception = 0;
 
 232     JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
 
 234         [_context notifyException:exception];
 
 238     JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName);
 
 239     JSObjectSetProperty([_context JSGlobalContextRef], object, name, objectToValue(_context, value), 0, &exception);
 
 240     JSStringRelease(name);
 
 242         [_context notifyException:exception];
 
 247 - (BOOL)deleteProperty:(NSString *)propertyName
 
 249     JSValueRef exception = 0;
 
 250     JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
 
 252         return [_context boolFromNotifyException:exception];
 
 254     JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName);
 
 255     BOOL result = JSObjectDeleteProperty([_context JSGlobalContextRef], object, name, &exception);
 
 256     JSStringRelease(name);
 
 258         return [_context boolFromNotifyException:exception];
 
 263 - (BOOL)hasProperty:(NSString *)propertyName
 
 265     JSValueRef exception = 0;
 
 266     JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
 
 268         return [_context boolFromNotifyException:exception];
 
 270     JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName);
 
 271     BOOL result = JSObjectHasProperty([_context JSGlobalContextRef], object, name);
 
 272     JSStringRelease(name);
 
 276 - (void)defineProperty:(NSString *)property descriptor:(id)descriptor
 
 278     [[_context globalObject][@"Object"] invokeMethod:@"defineProperty" withArguments:@[ self, property, descriptor ]];
 
 281 - (JSValue *)valueAtIndex:(NSUInteger)index
 
 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]];
 
 288     JSValueRef exception = 0;
 
 289     JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
 
 291         return [_context valueFromNotifyException:exception];
 
 293     JSValueRef result = JSObjectGetPropertyAtIndex([_context JSGlobalContextRef], object, (unsigned)index, &exception);
 
 295         return [_context valueFromNotifyException:exception];
 
 297     return [JSValue valueWithJSValueRef:result inContext:_context];
 
 300 - (void)setValue:(id)value atIndex:(NSUInteger)index
 
 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]];
 
 307     JSValueRef exception = 0;
 
 308     JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
 
 310         [_context notifyException:exception];
 
 314     JSObjectSetPropertyAtIndex([_context JSGlobalContextRef], object, (unsigned)index, objectToValue(_context, value), &exception);
 
 316         [_context notifyException:exception];
 
 323     return JSValueIsUndefined([_context JSGlobalContextRef], m_value);
 
 328     return JSValueIsNull([_context JSGlobalContextRef], m_value);
 
 333     return JSValueIsBoolean([_context JSGlobalContextRef], m_value);
 
 338     return JSValueIsNumber([_context JSGlobalContextRef], m_value);
 
 343     return JSValueIsString([_context JSGlobalContextRef], m_value);
 
 348     return JSValueIsObject([_context JSGlobalContextRef], m_value);
 
 351 - (BOOL)isEqualToObject:(id)value
 
 353     return JSValueIsStrictEqual([_context JSGlobalContextRef], m_value, objectToValue(_context, value));
 
 356 - (BOOL)isEqualWithTypeCoercionToObject:(id)value
 
 358     JSValueRef exception = 0;
 
 359     BOOL result = JSValueIsEqual([_context JSGlobalContextRef], m_value, objectToValue(_context, value), &exception);
 
 361         return [_context boolFromNotifyException:exception];
 
 366 - (BOOL)isInstanceOf:(id)value
 
 368     JSValueRef exception = 0;
 
 369     JSObjectRef constructor = JSValueToObject([_context JSGlobalContextRef], objectToValue(_context, value), &exception);
 
 371         return [_context boolFromNotifyException:exception];
 
 373     BOOL result = JSValueIsInstanceOfConstructor([_context JSGlobalContextRef], m_value, constructor, &exception);
 
 375         return [_context boolFromNotifyException:exception];
 
 380 - (JSValue *)callWithArguments:(NSArray *)argumentArray
 
 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]);
 
 387     JSValueRef exception = 0;
 
 388     JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
 
 390         return [_context valueFromNotifyException:exception];
 
 392     JSValueRef result = JSObjectCallAsFunction([_context JSGlobalContextRef], object, 0, argumentCount, arguments, &exception);
 
 394         return [_context valueFromNotifyException:exception];
 
 396     return [JSValue valueWithJSValueRef:result inContext:_context];
 
 399 - (JSValue *)constructWithArguments:(NSArray *)argumentArray
 
 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]);
 
 406     JSValueRef exception = 0;
 
 407     JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
 
 409         return [_context valueFromNotifyException:exception];
 
 411     JSObjectRef result = JSObjectCallAsConstructor([_context JSGlobalContextRef], object, argumentCount, arguments, &exception);
 
 413         return [_context valueFromNotifyException:exception];
 
 415     return [JSValue valueWithJSValueRef:result inContext:_context];
 
 418 - (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments
 
 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]);
 
 425     JSValueRef exception = 0;
 
 426     JSObjectRef thisObject = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
 
 428         return [_context valueFromNotifyException:exception];
 
 430     JSStringRef name = JSStringCreateWithCFString((CFStringRef)method);
 
 431     JSValueRef function = JSObjectGetProperty([_context JSGlobalContextRef], thisObject, name, &exception);
 
 432     JSStringRelease(name);
 
 434         return [_context valueFromNotifyException:exception];
 
 436     JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], function, &exception);
 
 438         return [_context valueFromNotifyException:exception];
 
 440     JSValueRef result = JSObjectCallAsFunction([_context JSGlobalContextRef], object, thisObject, argumentCount, argumentArray, &exception);
 
 442         return [_context valueFromNotifyException:exception];
 
 444     return [JSValue valueWithJSValueRef:result inContext:_context];
 
 449 @implementation JSValue(StructSupport)
 
 454         static_cast<CGFloat>([self[@"x"] toDouble]),
 
 455         static_cast<CGFloat>([self[@"y"] toDouble])
 
 462         [[self[@"location"] toNumber] unsignedIntegerValue],
 
 463         [[self[@"length"] toNumber] unsignedIntegerValue]
 
 478         static_cast<CGFloat>([self[@"width"] toDouble]),
 
 479         static_cast<CGFloat>([self[@"height"] toDouble])
 
 483 + (JSValue *)valueWithPoint:(CGPoint)point inContext:(JSContext *)context
 
 485     return [JSValue valueWithObject:@{
 
 488     } inContext:context];
 
 491 + (JSValue *)valueWithRange:(NSRange)range inContext:(JSContext *)context
 
 493     return [JSValue valueWithObject:@{
 
 494         @"location":@(range.location),
 
 495         @"length":@(range.length)
 
 496     } inContext:context];
 
 499 + (JSValue *)valueWithRect:(CGRect)rect inContext:(JSContext *)context
 
 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];
 
 509 + (JSValue *)valueWithSize:(CGSize)size inContext:(JSContext *)context
 
 511     return [JSValue valueWithObject:@{
 
 512         @"width":@(size.width),
 
 513         @"height":@(size.height)
 
 514     } inContext:context];
 
 519 @implementation JSValue(SubscriptSupport)
 
 521 - (JSValue *)objectForKeyedSubscript:(id)key
 
 523     if (![key isKindOfClass:[NSString class]]) {
 
 524         key = [[JSValue valueWithObject:key inContext:_context] toString];
 
 526             return [JSValue valueWithUndefinedInContext:_context];
 
 529     return [self valueForProperty:(NSString *)key];
 
 532 - (JSValue *)objectAtIndexedSubscript:(NSUInteger)index
 
 534     return [self valueAtIndex:index];
 
 537 - (void)setObject:(id)object forKeyedSubscript:(NSObject <NSCopying> *)key
 
 539     if (![key isKindOfClass:[NSString class]]) {
 
 540         key = [[JSValue valueWithObject:key inContext:_context] toString];
 
 545     [self setValue:object forProperty:(NSString *)key];
 
 548 - (void)setObject:(id)object atIndexedSubscript:(NSUInteger)index
 
 550     [self setValue:object atIndex:index];
 
 555 inline bool isDate(JSObjectRef object, JSGlobalContextRef context)
 
 557     JSC::APIEntryShim entryShim(toJS(context));
 
 558     return toJS(object)->inherits(&JSC::DateInstance::s_info);
 
 561 inline bool isArray(JSObjectRef object, JSGlobalContextRef context)
 
 563     JSC::APIEntryShim entryShim(toJS(context));
 
 564     return toJS(object)->inherits(&JSC::JSArray::s_info);
 
 567 @implementation JSValue(Internal)
 
 569 enum ConversionType {
 
 575 class JSContainerConvertor {
 
 583     JSContainerConvertor(JSGlobalContextRef context)
 
 588     id convert(JSValueRef property);
 
 591     bool isWorkListEmpty() const { return !m_worklist.size(); }
 
 594     JSGlobalContextRef m_context;
 
 595     HashMap<JSValueRef, id> m_objectMap;
 
 596     Vector<Task> m_worklist;
 
 599 inline id JSContainerConvertor::convert(JSValueRef value)
 
 601     HashMap<JSValueRef, id>::iterator iter = m_objectMap.find(value);
 
 602     if (iter != m_objectMap.end())
 
 605     Task result = valueToObjectWithoutCopy(m_context, value);
 
 611 void JSContainerConvertor::add(Task task)
 
 613     m_objectMap.add(task.js, task.objc);
 
 614     if (task.type != ContainerNone)
 
 615         m_worklist.append(task);
 
 618 JSContainerConvertor::Task JSContainerConvertor::take()
 
 620     ASSERT(!isWorkListEmpty());
 
 621     Task last = m_worklist.last();
 
 622     m_worklist.removeLast();
 
 626 static JSContainerConvertor::Task valueToObjectWithoutCopy(JSGlobalContextRef context, JSValueRef value)
 
 628     if (!JSValueIsObject(context, value)) {
 
 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];
 
 646             ASSERT(JSValueIsUndefined(context, value));
 
 649         return (JSContainerConvertor::Task){ value, primitive, ContainerNone };
 
 652     JSObjectRef object = JSValueToObject(context, value, 0);
 
 654     if (id wrapped = tryUnwrapObjcObject(context, object))
 
 655         return (JSContainerConvertor::Task){ object, wrapped, ContainerNone };
 
 657     if (isDate(object, context))
 
 658         return (JSContainerConvertor::Task){ object, [NSDate dateWithTimeIntervalSince1970:JSValueToNumber(context, object, 0)], ContainerNone };
 
 660     if (isArray(object, context))
 
 661         return (JSContainerConvertor::Task){ object, [NSMutableArray array], ContainerArray };
 
 663     return (JSContainerConvertor::Task){ object, [NSMutableDictionary dictionary], ContainerDictionary };
 
 666 static id containerValueToObject(JSGlobalContextRef context, JSContainerConvertor::Task task)
 
 668     ASSERT(task.type != ContainerNone);
 
 669     JSContainerConvertor convertor(context);
 
 671     ASSERT(!convertor.isWorkListEmpty());
 
 674         JSContainerConvertor::Task current = convertor.take();
 
 675         ASSERT(JSValueIsObject(context, current.js));
 
 676         JSObjectRef js = JSValueToObject(context, current.js, 0);
 
 678         if (current.type == ContainerArray) {
 
 679             ASSERT([current.objc isKindOfClass:[NSMutableArray class]]);
 
 680             NSMutableArray *array = (NSMutableArray *)current.objc;
 
 682             JSStringRef lengthString = JSStringCreateWithUTF8CString("length");
 
 683             unsigned length = JSC::toUInt32(JSValueToNumber(context, JSObjectGetProperty(context, js, lengthString, 0), 0));
 
 684             JSStringRelease(lengthString);
 
 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]];
 
 691             ASSERT([current.objc isKindOfClass:[NSMutableDictionary class]]);
 
 692             NSMutableDictionary *dictionary = (NSMutableDictionary *)current.objc;
 
 694             JSPropertyNameArrayRef propertyNameArray = JSObjectCopyPropertyNames(context, js);
 
 695             size_t length = JSPropertyNameArrayGetCount(propertyNameArray);
 
 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;
 
 703             JSPropertyNameArrayRelease(propertyNameArray);
 
 706     } while (!convertor.isWorkListEmpty());
 
 711 id valueToObject(JSContext *context, JSValueRef value)
 
 713     JSContainerConvertor::Task result = valueToObjectWithoutCopy([context JSGlobalContextRef], value);
 
 714     if (result.type == ContainerNone)
 
 716     return containerValueToObject([context JSGlobalContextRef], result);
 
 719 id valueToNumber(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
 
 722     if (id wrapped = tryUnwrapObjcObject(context, value)) {
 
 723         if ([wrapped isKindOfClass:[NSNumber class]])
 
 727     if (JSValueIsBoolean(context, value))
 
 728         return JSValueToBoolean(context, value) ? @YES : @NO;
 
 730     double result = JSValueToNumber(context, value, exception);
 
 731     return [NSNumber numberWithDouble:*exception ? std::numeric_limits<double>::quiet_NaN() : result];
 
 734 id valueToString(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
 
 737     if (id wrapped = tryUnwrapObjcObject(context, value)) {
 
 738         if ([wrapped isKindOfClass:[NSString class]])
 
 742     JSStringRef jsstring = JSValueToStringCopy(context, value, exception);
 
 748     NSString *stringNS = [(NSString *)JSStringCopyCFString(kCFAllocatorDefault, jsstring) autorelease];
 
 749     JSStringRelease(jsstring);
 
 753 id valueToDate(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
 
 756     if (id wrapped = tryUnwrapObjcObject(context, value)) {
 
 757         if ([wrapped isKindOfClass:[NSDate class]])
 
 761     double result = JSValueToNumber(context, value, exception);
 
 762     return *exception ? nil : [NSDate dateWithTimeIntervalSince1970:result];
 
 765 id valueToArray(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
 
 768     if (id wrapped = tryUnwrapObjcObject(context, value)) {
 
 769         if ([wrapped isKindOfClass:[NSArray class]])
 
 773     if (JSValueIsObject(context, value))
 
 774         return containerValueToObject(context, (JSContainerConvertor::Task){ value, [NSMutableArray array], ContainerArray});
 
 776     if (!(JSValueIsNull(context, value) || JSValueIsUndefined(context, value)))
 
 777         *exception = toRef(JSC::createTypeError(toJS(context), "Cannot convert primitive to NSArray"));
 
 781 id valueToDictionary(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
 
 784     if (id wrapped = tryUnwrapObjcObject(context, value)) {
 
 785         if ([wrapped isKindOfClass:[NSDictionary class]])
 
 789     if (JSValueIsObject(context, value))
 
 790         return containerValueToObject(context, (JSContainerConvertor::Task){ value, [NSMutableDictionary dictionary], ContainerDictionary});
 
 792     if (!(JSValueIsNull(context, value) || JSValueIsUndefined(context, value)))
 
 793         *exception = toRef(JSC::createTypeError(toJS(context), "Cannot convert primitive to NSDictionary"));
 
 797 class ObjcContainerConvertor {
 
 805     ObjcContainerConvertor(JSContext *context)
 
 810     JSValueRef convert(id object);
 
 813     bool isWorkListEmpty() const { return !m_worklist.size(); }
 
 816     JSContext *m_context;
 
 817     HashMap<id, JSValueRef> m_objectMap;
 
 818     Vector<Task> m_worklist;
 
 821 JSValueRef ObjcContainerConvertor::convert(id object)
 
 825     auto it = m_objectMap.find(object);
 
 826     if (it != m_objectMap.end())
 
 829     ObjcContainerConvertor::Task task = objectToValueWithoutCopy(m_context, object);
 
 834 void ObjcContainerConvertor::add(ObjcContainerConvertor::Task task)
 
 836     m_objectMap.add(task.objc, task.js);
 
 837     if (task.type != ContainerNone)
 
 838         m_worklist.append(task);
 
 841 ObjcContainerConvertor::Task ObjcContainerConvertor::take()
 
 843     ASSERT(!isWorkListEmpty());
 
 844     Task last = m_worklist.last();
 
 845     m_worklist.removeLast();
 
 849 inline bool isNSBoolean(id object)
 
 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]];
 
 857 static ObjcContainerConvertor::Task objectToValueWithoutCopy(JSContext *context, id object)
 
 859     JSGlobalContextRef contextRef = [context JSGlobalContextRef];
 
 862         return (ObjcContainerConvertor::Task){ object, JSValueMakeUndefined(contextRef), ContainerNone };
 
 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 };
 
 868         if ([object isKindOfClass:[NSDictionary class]])
 
 869             return (ObjcContainerConvertor::Task){ object, JSObjectMake(contextRef, 0, 0), ContainerDictionary };
 
 871         if ([object isKindOfClass:[NSNull class]])
 
 872             return (ObjcContainerConvertor::Task){ object, JSValueMakeNull(contextRef), ContainerNone };
 
 874         if ([object isKindOfClass:[JSValue class]])
 
 875             return (ObjcContainerConvertor::Task){ object, ((JSValue *)object)->m_value, ContainerNone };
 
 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 };
 
 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 };
 
 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 };
 
 896         if ([object isKindOfClass:[JSManagedValue class]]) {
 
 897             JSValue *value = [static_cast<JSManagedValue *>(object) value];
 
 899                 return (ObjcContainerConvertor::Task) { object, JSValueMakeUndefined(contextRef), ContainerNone };
 
 900             return (ObjcContainerConvertor::Task){ object, value->m_value, ContainerNone };
 
 904     return (ObjcContainerConvertor::Task){ object, valueInternalValue([context wrapperForObjCObject:object]), ContainerNone };
 
 907 JSValueRef objectToValue(JSContext *context, id object)
 
 909     JSGlobalContextRef contextRef = [context JSGlobalContextRef];
 
 911     ObjcContainerConvertor::Task task = objectToValueWithoutCopy(context, object);
 
 912     if (task.type == ContainerNone)
 
 915     ObjcContainerConvertor convertor(context);
 
 917     ASSERT(!convertor.isWorkListEmpty());
 
 920         ObjcContainerConvertor::Task current = convertor.take();
 
 921         ASSERT(JSValueIsObject(contextRef, current.js));
 
 922         JSObjectRef js = JSValueToObject(contextRef, current.js, 0);
 
 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);
 
 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);
 
 943     } while (!convertor.isWorkListEmpty());
 
 948 JSValueRef valueInternalValue(JSValue * value)
 
 950     return value->m_value;
 
 953 + (JSValue *)valueWithJSValueRef:(JSValueRef)value inContext:(JSContext *)context
 
 955     return [context wrapperForJSObject:value];
 
 963 - (JSValue *)initWithValue:(JSValueRef)value inContext:(JSContext *)context
 
 965     if (!value || !context)
 
 972     _context = [context retain];
 
 974     JSValueProtect([_context JSGlobalContextRef], m_value);
 
 978 struct StructTagHandler {
 
 982 typedef HashMap<String, StructTagHandler> StructHandlers;
 
 984 static StructHandlers* createStructHandlerMap()
 
 986     StructHandlers* structHandlers = new StructHandlers();
 
 988     size_t valueWithXinContextLength = strlen("valueWithX:inContext:");
 
 989     size_t toXLength = strlen("toX");
 
 991     // Step 1: find all valueWith<Foo>:inContext: class methods in JSValue.
 
 992     forEachMethodInClass(object_getClass([JSValue class]), ^(Method method){
 
 993         SEL selector = method_getName(method);
 
 994         const char* name = sel_getName(selector);
 
 995         size_t nameLength = strlen(name);
 
 996         // Check for valueWith<Foo>:context:
 
 997         if (nameLength < valueWithXinContextLength || memcmp(name, "valueWith", 9) || memcmp(name + nameLength - 11, ":inContext:", 11))
 
 999         // Check for [ id, SEL, <type>, <contextType> ]
 
1000         if (method_getNumberOfArguments(method) != 4)
 
1003         // Check 2nd argument type is "@"
 
1004         char* secondType = method_copyArgumentType(method, 3);
 
1005         if (strcmp(secondType, "@") != 0) {
 
1010         // Check result type is also "@"
 
1011         method_getReturnType(method, idType, 3);
 
1012         if (strcmp(idType, "@") != 0)
 
1014         char* type = method_copyArgumentType(method, 2);
 
1015         structHandlers->add(StringImpl::create(type), (StructTagHandler){ selector, 0 });
 
1019     // Step 2: find all to<Foo> instance methods in JSValue.
 
1020     forEachMethodInClass([JSValue class], ^(Method method){
 
1021         SEL selector = method_getName(method);
 
1022         const char* name = sel_getName(selector);
 
1023         size_t nameLength = strlen(name);
 
1024         // Check for to<Foo>
 
1025         if (nameLength < toXLength || memcmp(name, "to", 2))
 
1027         // Check for [ id, SEL ]
 
1028         if (method_getNumberOfArguments(method) != 2)
 
1030         // Try to find a matching valueWith<Foo>:context: method.
 
1031         char* type = method_copyReturnType(method);
 
1033         StructHandlers::iterator iter = structHandlers->find(type);
 
1035         if (iter == structHandlers->end())
 
1037         StructTagHandler& handler = iter->value;
 
1039         // check that strlen(<foo>) == strlen(<Foo>)
 
1040         const char* valueWithName = sel_getName(handler.typeToValueSEL);
 
1041         size_t valueWithLength = strlen(valueWithName);
 
1042         if (valueWithLength - valueWithXinContextLength != nameLength - toXLength)
 
1044         // Check that <Foo> == <Foo>
 
1045         if (memcmp(valueWithName + 9, name + 2, nameLength - toXLength - 1))
 
1047         handler.valueToTypeSEL = selector;
 
1050     // Step 3: clean up - remove entries where we found prospective valueWith<Foo>:inContext: conversions, but no matching to<Foo> methods.
 
1051     typedef HashSet<String> RemoveSet;
 
1052     RemoveSet removeSet;
 
1053     for (StructHandlers::iterator iter = structHandlers->begin(); iter != structHandlers->end(); ++iter) {
 
1054         StructTagHandler& handler = iter->value;
 
1055         if (!handler.valueToTypeSEL)
 
1056             removeSet.add(iter->key);
 
1059     for (RemoveSet::iterator iter = removeSet.begin(); iter != removeSet.end(); ++iter)
 
1060         structHandlers->remove(*iter);
 
1062     return structHandlers;
 
1065 static StructTagHandler* handerForStructTag(const char* encodedType)
 
1067     static SpinLock handerForStructTagLock = SPINLOCK_INITIALIZER;
 
1068     SpinLockHolder lockHolder(&handerForStructTagLock);
 
1070     static StructHandlers* structHandlers = createStructHandlerMap();
 
1072     StructHandlers::iterator iter = structHandlers->find(encodedType);
 
1073     if (iter == structHandlers->end())
 
1075     return &iter->value;
 
1078 + (SEL)selectorForStructToValue:(const char *)structTag
 
1080     StructTagHandler* handler = handerForStructTag(structTag);
 
1081     return handler ? handler->typeToValueSEL : nil;
 
1084 + (SEL)selectorForValueToStruct:(const char *)structTag
 
1086     StructTagHandler* handler = handerForStructTag(structTag);
 
1087     return handler ? handler->valueToTypeSEL : nil;
 
1092     JSValueUnprotect([_context JSGlobalContextRef], m_value);
 
1098 - (NSString *)description
 
1100     if (id wrapped = tryUnwrapObjcObject([_context JSGlobalContextRef], m_value))
 
1101         return [wrapped description];
 
1102     return [self toString];
 
1105 NSInvocation *typeToValueInvocationFor(const char* encodedType)
 
1107     SEL selector = [JSValue selectorForStructToValue:encodedType];
 
1111     const char* methodTypes = method_getTypeEncoding(class_getClassMethod([JSValue class], selector));
 
1112     NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:methodTypes]];
 
1113     [invocation setSelector:selector];
 
1117 NSInvocation *valueToTypeInvocationFor(const char* encodedType)
 
1119     SEL selector = [JSValue selectorForValueToStruct:encodedType];
 
1123     const char* methodTypes = method_getTypeEncoding(class_getInstanceMethod([JSValue class], selector));
 
1124     NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:methodTypes]];
 
1125     [invocation setSelector:selector];