JSWrapperMap.mm   [plain text]


/*
 * Copyright (C) 2013-2015, 2017 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */

#include "config.h"
#import "JavaScriptCore.h"

#if JSC_OBJC_API_ENABLED
#import "APICast.h"
#import "JSAPIWrapperObject.h"
#import "JSCInlines.h"
#import "JSCallbackObject.h"
#import "JSContextInternal.h"
#import "JSWrapperMap.h"
#import "ObjCCallbackFunction.h"
#import "ObjcRuntimeExtras.h"
#import "ObjectConstructor.h"
#import "WeakGCMap.h"
#import "WeakGCMapInlines.h"
#import <wtf/Vector.h>
#import <wtf/spi/cocoa/NSMapTableSPI.h>
#import <wtf/spi/darwin/dyldSPI.h>

#include <mach-o/dyld.h>

#if PLATFORM(APPLETV)
#else
static const int32_t firstJavaScriptCoreVersionWithInitConstructorSupport = 0x21A0400; // 538.4.0
#if PLATFORM(IOS)
static const uint32_t firstSDKVersionWithInitConstructorSupport = DYLD_IOS_VERSION_10_0;
#elif PLATFORM(MAC)
static const uint32_t firstSDKVersionWithInitConstructorSupport = 0xA0A00; // OSX 10.10.0
#endif
#endif

@class JSObjCClassInfo;

@interface JSWrapperMap () 

- (JSObjCClassInfo*)classInfoForClass:(Class)cls;

@end

static const constexpr unsigned InitialBufferSize { 256 };

// Default conversion of selectors to property names.
// All semicolons are removed, lowercase letters following a semicolon are capitalized.
static NSString *selectorToPropertyName(const char* start)
{
    // Use 'index' to check for colons, if there are none, this is easy!
    const char* firstColon = strchr(start, ':');
    if (!firstColon)
        return [NSString stringWithUTF8String:start];

    // 'header' is the length of string up to the first colon.
    size_t header = firstColon - start;
    // The new string needs to be long enough to hold 'header', plus the remainder of the string, excluding
    // at least one ':', but including a '\0'. (This is conservative if there are more than one ':').
    Vector<char, InitialBufferSize> buffer(header + strlen(firstColon + 1) + 1);
    // Copy 'header' characters, set output to point to the end of this & input to point past the first ':'.
    memcpy(buffer.data(), start, header);
    char* output = buffer.data() + header;
    const char* input = start + header + 1;

    // On entry to the loop, we have already skipped over a ':' from the input.
    while (true) {
        char c;
        // Skip over any additional ':'s. We'll leave c holding the next character after the
        // last ':', and input pointing past c.
        while ((c = *(input++)) == ':');
        // Copy the character, converting to upper case if necessary.
        // If the character we copy is '\0', then we're done!
        if (!(*(output++) = toASCIIUpper(c)))
            goto done;
        // Loop over characters other than ':'.
        while ((c = *(input++)) != ':') {
            // Copy the character.
            // If the character we copy is '\0', then we're done!
            if (!(*(output++) = c))
                goto done;
        }
        // If we get here, we've consumed a ':' - wash, rinse, repeat.
    }
done:
    return [NSString stringWithUTF8String:buffer.data()];
}

static bool constructorHasInstance(JSContextRef ctx, JSObjectRef constructorRef, JSValueRef possibleInstance, JSValueRef*)
{
    JSC::ExecState* exec = toJS(ctx);
    JSC::VM& vm = exec->vm();
    JSC::JSLockHolder locker(vm);

    JSC::JSObject* constructor = toJS(constructorRef);
    JSC::JSValue instance = toJS(exec, possibleInstance);
    return JSC::JSObject::defaultHasInstance(exec, instance, constructor->get(exec, vm.propertyNames->prototype));
}

static JSC::JSObject* makeWrapper(JSContextRef ctx, JSClassRef jsClass, id wrappedObject)
{
    JSC::ExecState* exec = toJS(ctx);
    JSC::VM& vm = exec->vm();
    JSC::JSLockHolder locker(vm);

    ASSERT(jsClass);
    JSC::JSCallbackObject<JSC::JSAPIWrapperObject>* object = JSC::JSCallbackObject<JSC::JSAPIWrapperObject>::create(exec, exec->lexicalGlobalObject(), exec->lexicalGlobalObject()->objcWrapperObjectStructure(), jsClass, 0);
    object->setWrappedObject((__bridge void*)wrappedObject);
    if (JSC::JSObject* prototype = jsClass->prototype(exec))
        object->setPrototypeDirect(vm, prototype);

    return object;
}

// Make an object that is in all ways a completely vanilla JavaScript object,
// other than that it has a native brand set that will be displayed by the default
// Object.prototype.toString conversion.
static JSC::JSObject *objectWithCustomBrand(JSContext *context, NSString *brand, Class cls = 0)
{
    JSClassDefinition definition;
    definition = kJSClassDefinitionEmpty;
    definition.className = [brand UTF8String];
    JSClassRef classRef = JSClassCreate(&definition);
    JSC::JSObject* result = makeWrapper([context JSGlobalContextRef], classRef, cls);
    JSClassRelease(classRef);
    return result;
}

static JSC::JSObject *constructorWithCustomBrand(JSContext *context, NSString *brand, Class cls)
{
    JSClassDefinition definition;
    definition = kJSClassDefinitionEmpty;
    definition.className = [brand UTF8String];
    definition.hasInstance = constructorHasInstance;
    JSClassRef classRef = JSClassCreate(&definition);
    JSC::JSObject* result = makeWrapper([context JSGlobalContextRef], classRef, cls);
    JSClassRelease(classRef);
    return result;
}

// Look for @optional properties in the prototype containing a selector to property
// name mapping, separated by a __JS_EXPORT_AS__ delimiter.
static NSMutableDictionary *createRenameMap(Protocol *protocol, BOOL isInstanceMethod)
{
    NSMutableDictionary *renameMap = [[NSMutableDictionary alloc] init];

    forEachMethodInProtocol(protocol, NO, isInstanceMethod, ^(SEL sel, const char*){
        NSString *rename = @(sel_getName(sel));
        NSRange range = [rename rangeOfString:@"__JS_EXPORT_AS__"];
        if (range.location == NSNotFound)
            return;
        NSString *selector = [rename substringToIndex:range.location];
        NSUInteger begin = range.location + range.length;
        NSUInteger length = [rename length] - begin - 1;
        NSString *name = [rename substringWithRange:(NSRange){ begin, length }];
        renameMap[selector] = name;
    });

    return renameMap;
}

inline void putNonEnumerable(JSValue *base, NSString *propertyName, JSValue *value)
{
    [base defineProperty:propertyName descriptor:@{
        JSPropertyDescriptorValueKey: value,
        JSPropertyDescriptorWritableKey: @YES,
        JSPropertyDescriptorEnumerableKey: @NO,
        JSPropertyDescriptorConfigurableKey: @YES
    }];
}

static bool isInitFamilyMethod(NSString *name)
{
    NSUInteger i = 0;

    // Skip over initial underscores.
    for (; i < [name length]; ++i) {
        if ([name characterAtIndex:i] != '_')
            break;
    }

    // Match 'init'.
    NSUInteger initIndex = 0;
    NSString* init = @"init";
    for (; i < [name length] && initIndex < [init length]; ++i, ++initIndex) {
        if ([name characterAtIndex:i] != [init characterAtIndex:initIndex])
            return false;
    }

    // We didn't match all of 'init'.
    if (initIndex < [init length])
        return false;

    // If we're at the end or the next character is a capital letter then this is an init-family selector.
    return i == [name length] || [[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember:[name characterAtIndex:i]]; 
}

static bool shouldSkipMethodWithName(NSString *name)
{
    // For clients that don't support init-based constructors just copy 
    // over the init method as we would have before.
    if (!supportsInitMethodConstructors())
        return false;

    // Skip over init family methods because we handle those specially 
    // for the purposes of hooking up the constructor correctly.
    return isInitFamilyMethod(name);
}

// This method will iterate over the set of required methods in the protocol, and:
//  * Determine a property name (either via a renameMap or default conversion).
//  * If an accessorMap is provided, and contains this name, store the method in the map.
//  * Otherwise, if the object doesn't already contain a property with name, create it.
static void copyMethodsToObject(JSContext *context, Class objcClass, Protocol *protocol, BOOL isInstanceMethod, JSValue *object, NSMutableDictionary *accessorMethods = nil)
{
    NSMutableDictionary *renameMap = createRenameMap(protocol, isInstanceMethod);

    forEachMethodInProtocol(protocol, YES, isInstanceMethod, ^(SEL sel, const char* types){
        const char* nameCStr = sel_getName(sel);
        NSString *name = @(nameCStr);

        if (shouldSkipMethodWithName(name))
            return;

        if (accessorMethods && accessorMethods[name]) {
            JSObjectRef method = objCCallbackFunctionForMethod(context, objcClass, protocol, isInstanceMethod, sel, types);
            if (!method)
                return;
            accessorMethods[name] = [JSValue valueWithJSValueRef:method inContext:context];
        } else {
            name = renameMap[name];
            if (!name)
                name = selectorToPropertyName(nameCStr);
            if ([object hasProperty:name])
                return;
            JSObjectRef method = objCCallbackFunctionForMethod(context, objcClass, protocol, isInstanceMethod, sel, types);
            if (method)
                putNonEnumerable(object, name, [JSValue valueWithJSValueRef:method inContext:context]);
        }
    });

    [renameMap release];
}

struct Property {
    const char* name;
    RetainPtr<NSString> getterName;
    RetainPtr<NSString> setterName;
};

static bool parsePropertyAttributes(objc_property_t objcProperty, Property& property)
{
    bool readonly = false;
    unsigned attributeCount;
    auto attributes = adoptSystem<objc_property_attribute_t[]>(property_copyAttributeList(objcProperty, &attributeCount));
    if (attributeCount) {
        for (unsigned i = 0; i < attributeCount; ++i) {
            switch (*(attributes[i].name)) {
            case 'G':
                property.getterName = @(attributes[i].value);
                break;
            case 'S':
                property.setterName = @(attributes[i].value);
                break;
            case 'R':
                readonly = true;
                break;
            default:
                break;
            }
        }
    }
    return readonly;
}

static RetainPtr<NSString> makeSetterName(const char* name)
{
    size_t nameLength = strlen(name);
    // "set" Name ":\0"  => nameLength + 5.
    Vector<char, 128> buffer(nameLength + 5);
    buffer[0] = 's';
    buffer[1] = 'e';
    buffer[2] = 't';
    buffer[3] = toASCIIUpper(*name);
    memcpy(buffer.data() + 4, name + 1, nameLength - 1);
    buffer[nameLength + 3] = ':';
    buffer[nameLength + 4] = '\0';
    return @(buffer.data());
}

static void copyPrototypeProperties(JSContext *context, Class objcClass, Protocol *protocol, JSValue *prototypeValue)
{
    // First gather propreties into this list, then handle the methods (capturing the accessor methods).
    __block Vector<Property> propertyList;

    // Map recording the methods used as getters/setters.
    NSMutableDictionary *accessorMethods = [NSMutableDictionary dictionary];

    // Useful value.
    JSValue *undefined = [JSValue valueWithUndefinedInContext:context];

    forEachPropertyInProtocol(protocol, ^(objc_property_t objcProperty) {
        const char* name = property_getName(objcProperty);
        Property property { name, nullptr, nullptr };
        bool readonly = parsePropertyAttributes(objcProperty, property);

        // Add the names of the getter & setter methods to
        if (!property.getterName)
            property.getterName = @(name);
        accessorMethods[property.getterName.get()] = undefined;
        if (!readonly) {
            if (!property.setterName)
                property.setterName = makeSetterName(name);
            accessorMethods[property.setterName.get()] = undefined;
        }

        // Add the properties to a list.
        propertyList.append(WTFMove(property));
    });

    // Copy methods to the prototype, capturing accessors in the accessorMethods map.
    copyMethodsToObject(context, objcClass, protocol, YES, prototypeValue, accessorMethods);

    // Iterate the propertyList & generate accessor properties.
    for (auto& property : propertyList) {
        JSValue* getter = accessorMethods[property.getterName.get()];
        ASSERT(![getter isUndefined]);

        JSValue* setter = undefined;
        if (property.setterName) {
            setter = accessorMethods[property.setterName.get()];
            ASSERT(![setter isUndefined]);
        }
        
        [prototypeValue defineProperty:@(property.name) descriptor:@{
            JSPropertyDescriptorGetKey: getter,
            JSPropertyDescriptorSetKey: setter,
            JSPropertyDescriptorEnumerableKey: @NO,
            JSPropertyDescriptorConfigurableKey: @YES
        }];
    }
}

@interface JSObjCClassInfo : NSObject {
    Class m_class;
    bool m_block;
    JSClassRef m_classRef;
    JSC::Weak<JSC::JSObject> m_prototype;
    JSC::Weak<JSC::JSObject> m_constructor;
    JSC::Weak<JSC::Structure> m_structure;
}

- (instancetype)initForClass:(Class)cls;
- (JSC::JSObject *)wrapperForObject:(id)object inContext:(JSContext *)context;
- (JSC::JSObject *)constructorInContext:(JSContext *)context;
- (JSC::JSObject *)prototypeInContext:(JSContext *)context;

@end

@implementation JSObjCClassInfo

- (instancetype)initForClass:(Class)cls
{
    self = [super init];
    if (!self)
        return nil;

    const char* className = class_getName(cls);
    m_class = cls;
    m_block = [cls isSubclassOfClass:getNSBlockClass()];
    JSClassDefinition definition;
    definition = kJSClassDefinitionEmpty;
    definition.className = className;
    m_classRef = JSClassCreate(&definition);

    return self;
}

- (void)dealloc
{
    JSClassRelease(m_classRef);
    [super dealloc];
}

static JSC::JSObject* allocateConstructorForCustomClass(JSContext *context, const char* className, Class cls)
{
    if (!supportsInitMethodConstructors())
        return constructorWithCustomBrand(context, [NSString stringWithFormat:@"%sConstructor", className], cls);

    // For each protocol that the class implements, gather all of the init family methods into a hash table.
    __block HashMap<String, Protocol *> initTable;
    Protocol *exportProtocol = getJSExportProtocol();
    for (Class currentClass = cls; currentClass; currentClass = class_getSuperclass(currentClass)) {
        forEachProtocolImplementingProtocol(currentClass, exportProtocol, ^(Protocol *protocol, bool&) {
            forEachMethodInProtocol(protocol, YES, YES, ^(SEL selector, const char*) {
                const char* name = sel_getName(selector);
                if (!isInitFamilyMethod(@(name)))
                    return;
                initTable.set(name, protocol);
            });
        });
    }

    for (Class currentClass = cls; currentClass; currentClass = class_getSuperclass(currentClass)) {
        __block unsigned numberOfInitsFound = 0;
        __block SEL initMethod = 0;
        __block Protocol *initProtocol = 0;
        __block const char* types = 0;
        forEachMethodInClass(currentClass, ^(Method method) {
            SEL selector = method_getName(method);
            const char* name = sel_getName(selector);
            auto iter = initTable.find(name);

            if (iter == initTable.end())
                return;

            numberOfInitsFound++;
            initMethod = selector;
            initProtocol = iter->value;
            types = method_getTypeEncoding(method);
        });

        if (!numberOfInitsFound)
            continue;

        if (numberOfInitsFound > 1) {
            NSLog(@"ERROR: Class %@ exported more than one init family method via JSExport. Class %@ will not have a callable JavaScript constructor function.", cls, cls);
            break;
        }

        JSObjectRef method = objCCallbackFunctionForInit(context, cls, initProtocol, initMethod, types);
        return toJS(method);
    }
    return constructorWithCustomBrand(context, [NSString stringWithFormat:@"%sConstructor", className], cls);
}

typedef std::pair<JSC::JSObject*, JSC::JSObject*> ConstructorPrototypePair;

- (ConstructorPrototypePair)allocateConstructorAndPrototypeInContext:(JSContext *)context
{
    JSObjCClassInfo* superClassInfo = [context.wrapperMap classInfoForClass:class_getSuperclass(m_class)];

    ASSERT(!m_constructor || !m_prototype);
    ASSERT((m_class == [NSObject class]) == !superClassInfo);

    JSC::JSObject* jsPrototype = m_prototype.get();
    JSC::JSObject* jsConstructor = m_constructor.get();

    if (!superClassInfo) {
        JSC::JSGlobalObject* globalObject = toJSGlobalObject([context JSGlobalContextRef]);
        if (!jsConstructor)
            jsConstructor = globalObject->objectConstructor();

        if (!jsPrototype)
            jsPrototype = globalObject->objectPrototype();
    } else {
        const char* className = class_getName(m_class);

        // Create or grab the prototype/constructor pair.
        if (!jsPrototype)
            jsPrototype = objectWithCustomBrand(context, [NSString stringWithFormat:@"%sPrototype", className]);

        if (!jsConstructor)
            jsConstructor = allocateConstructorForCustomClass(context, className, m_class);

        JSValue* prototype = [JSValue valueWithJSValueRef:toRef(jsPrototype) inContext:context];
        JSValue* constructor = [JSValue valueWithJSValueRef:toRef(jsConstructor) inContext:context];
        putNonEnumerable(prototype, @"constructor", constructor);
        putNonEnumerable(constructor, @"prototype", prototype);

        Protocol *exportProtocol = getJSExportProtocol();
        forEachProtocolImplementingProtocol(m_class, exportProtocol, ^(Protocol *protocol, bool&){
            copyPrototypeProperties(context, m_class, protocol, prototype);
            copyMethodsToObject(context, m_class, protocol, NO, constructor);
        });

        // Set [Prototype].
        JSC::JSObject* superClassPrototype = [superClassInfo prototypeInContext:context];
        JSObjectSetPrototype([context JSGlobalContextRef], toRef(jsPrototype), toRef(superClassPrototype));
    }

    m_prototype = jsPrototype;
    m_constructor = jsConstructor;
    return ConstructorPrototypePair(jsConstructor, jsPrototype);
}

- (JSC::JSObject*)wrapperForObject:(id)object inContext:(JSContext *)context
{
    ASSERT([object isKindOfClass:m_class]);
    ASSERT(m_block == [object isKindOfClass:getNSBlockClass()]);
    if (m_block) {
        if (JSObjectRef method = objCCallbackFunctionForBlock(context, object)) {
            JSValue *constructor = [JSValue valueWithJSValueRef:method inContext:context];
            JSValue *prototype = [JSValue valueWithNewObjectInContext:context];
            putNonEnumerable(constructor, @"prototype", prototype);
            putNonEnumerable(prototype, @"constructor", constructor);
            return toJS(method);
        }
    }

    JSC::Structure* structure = [self structureInContext:context];

    JSC::ExecState* exec = toJS([context JSGlobalContextRef]);
    JSC::VM& vm = exec->vm();
    JSC::JSLockHolder locker(vm);

    auto wrapper = JSC::JSCallbackObject<JSC::JSAPIWrapperObject>::create(exec, exec->lexicalGlobalObject(), structure, m_classRef, 0);
    wrapper->setWrappedObject((__bridge void*)object);
    return wrapper;
}

- (JSC::JSObject*)constructorInContext:(JSContext *)context
{
    JSC::JSObject* constructor = m_constructor.get();
    if (!constructor)
        constructor = [self allocateConstructorAndPrototypeInContext:context].first;
    ASSERT(!!constructor);
    return constructor;
}

- (JSC::JSObject*)prototypeInContext:(JSContext *)context
{
    JSC::JSObject* prototype = m_prototype.get();
    if (!prototype)
        prototype = [self allocateConstructorAndPrototypeInContext:context].second;
    ASSERT(!!prototype);
    return prototype;
}

- (JSC::Structure*)structureInContext:(JSContext *)context
{
    JSC::Structure* structure = m_structure.get();
    if (structure)
        return structure;

    JSC::ExecState* exec = toJS([context JSGlobalContextRef]);
    JSC::JSGlobalObject* globalObject = toJSGlobalObject([context JSGlobalContextRef]);
    JSC::JSObject* prototype = [self prototypeInContext:context];
    m_structure = JSC::JSCallbackObject<JSC::JSAPIWrapperObject>::createStructure(exec->vm(), globalObject, prototype);

    return m_structure.get();
}

@end

@implementation JSWrapperMap {
    NSMutableDictionary *m_classMap;
    std::unique_ptr<JSC::WeakGCMap<id, JSC::JSObject>> m_cachedJSWrappers;
    NSMapTable *m_cachedObjCWrappers;
}

- (instancetype)initWithGlobalContextRef:(JSGlobalContextRef)context
{
    self = [super init];
    if (!self)
        return nil;

    NSPointerFunctionsOptions keyOptions = NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality;
    NSPointerFunctionsOptions valueOptions = NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPersonality;
    m_cachedObjCWrappers = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:valueOptions capacity:0];

    m_cachedJSWrappers = std::make_unique<JSC::WeakGCMap<id, JSC::JSObject>>(toJS(context)->vm());

    ASSERT(!toJSGlobalObject(context)->wrapperMap());
    toJSGlobalObject(context)->setWrapperMap(self);
    m_classMap = [[NSMutableDictionary alloc] init];
    return self;
}

- (void)dealloc
{
    [m_cachedObjCWrappers release];
    [m_classMap release];
    [super dealloc];
}

- (JSObjCClassInfo*)classInfoForClass:(Class)cls
{
    if (!cls)
        return nil;

    // Check if we've already created a JSObjCClassInfo for this Class.
    if (JSObjCClassInfo* classInfo = (JSObjCClassInfo*)m_classMap[cls])
        return classInfo;

    // Skip internal classes beginning with '_' - just copy link to the parent class's info.
    if ('_' == *class_getName(cls)) {
        bool conformsToExportProtocol = false;
        forEachProtocolImplementingProtocol(cls, getJSExportProtocol(), [&conformsToExportProtocol](Protocol *, bool& stop) {
            conformsToExportProtocol = true;
            stop = true;
        });

        if (!conformsToExportProtocol)
            return m_classMap[cls] = [self classInfoForClass:class_getSuperclass(cls)];
    }

    return m_classMap[cls] = [[[JSObjCClassInfo alloc] initForClass:cls] autorelease];
}

- (JSValue *)jsWrapperForObject:(id)object inContext:(JSContext *)context
{
    ASSERT(toJSGlobalObject([context JSGlobalContextRef])->wrapperMap() == self);
    JSC::JSObject* jsWrapper = m_cachedJSWrappers->get(object);
    if (jsWrapper)
        return [JSValue valueWithJSValueRef:toRef(jsWrapper) inContext:context];

    if (class_isMetaClass(object_getClass(object)))
        jsWrapper = [[self classInfoForClass:(Class)object] constructorInContext:context];
    else {
        JSObjCClassInfo* classInfo = [self classInfoForClass:[object class]];
        jsWrapper = [classInfo wrapperForObject:object inContext:context];
    }

    // FIXME: https://bugs.webkit.org/show_bug.cgi?id=105891
    // This general approach to wrapper caching is pretty effective, but there are a couple of problems:
    // (1) For immortal objects JSValues will effectively leak and this results in error output being logged - we should avoid adding associated objects to immortal objects.
    // (2) A long lived object may rack up many JSValues. When the contexts are released these will unprotect the associated JavaScript objects,
    //     but still, would probably nicer if we made it so that only one associated object was required, broadcasting object dealloc.
    m_cachedJSWrappers->set(object, jsWrapper);
    return [JSValue valueWithJSValueRef:toRef(jsWrapper) inContext:context];
}

- (JSValue *)objcWrapperForJSValueRef:(JSValueRef)value inContext:context
{
    ASSERT(toJSGlobalObject([context JSGlobalContextRef])->wrapperMap() == self);
    JSValue *wrapper = (__bridge JSValue *)NSMapGet(m_cachedObjCWrappers, value);
    if (!wrapper) {
        wrapper = [[[JSValue alloc] initWithValue:value inContext:context] autorelease];
        NSMapInsert(m_cachedObjCWrappers, value, (__bridge void*)wrapper);
    }
    return wrapper;
}

@end

id tryUnwrapObjcObject(JSGlobalContextRef context, JSValueRef value)
{
    if (!JSValueIsObject(context, value))
        return nil;
    JSValueRef exception = 0;
    JSObjectRef object = JSValueToObject(context, value, &exception);
    ASSERT(!exception);
    JSC::JSLockHolder locker(toJS(context));
    JSC::VM& vm = toJS(context)->vm();
    if (toJS(object)->inherits<JSC::JSCallbackObject<JSC::JSAPIWrapperObject>>(vm))
        return (__bridge id)JSC::jsCast<JSC::JSAPIWrapperObject*>(toJS(object))->wrappedObject();
    if (id target = tryUnwrapConstructor(&vm, object))
        return target;
    return nil;
}

// This class ensures that the JSExport protocol is registered with the runtime.
NS_ROOT_CLASS @interface JSExport <JSExport>
@end
@implementation JSExport
@end

bool supportsInitMethodConstructors()
{
#if PLATFORM(APPLETV)
    // There are no old clients on Apple TV, so there's no need for backwards compatibility.
    return true;
#else
    // First check to see the version of JavaScriptCore we directly linked against.
    static int32_t versionOfLinkTimeJavaScriptCore = 0;
    if (!versionOfLinkTimeJavaScriptCore)
        versionOfLinkTimeJavaScriptCore = NSVersionOfLinkTimeLibrary("JavaScriptCore");
    // Only do the link time version comparison if we linked directly with JavaScriptCore
    if (versionOfLinkTimeJavaScriptCore != -1)
        return versionOfLinkTimeJavaScriptCore >= firstJavaScriptCoreVersionWithInitConstructorSupport;

    // If we didn't link directly with JavaScriptCore,
    // base our check on what SDK was used to build the application.
    static uint32_t programSDKVersion = 0;
    if (!programSDKVersion)
        programSDKVersion = dyld_get_program_sdk_version();

    return programSDKVersion >= firstSDKVersionWithInitConstructorSupport;
#endif
}

Protocol *getJSExportProtocol()
{
    static Protocol *protocol = objc_getProtocol("JSExport");
    return protocol;
}

Class getNSBlockClass()
{
    static Class cls = objc_getClass("NSBlock");
    return cls;
}

#endif