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#import "JavaScriptCore.h"
28
29#if JSC_OBJC_API_ENABLED
30
31#import "APICast.h"
32#import "JSAPIWrapperObject.h"
33#import "JSCallbackObject.h"
34#import "JSContextInternal.h"
35#import "JSWrapperMap.h"
36#import "ObjCCallbackFunction.h"
37#import "ObjcRuntimeExtras.h"
38#import "JSCInlines.h"
39#import "WeakGCMap.h"
40#import <wtf/TCSpinLock.h>
41#import <wtf/Vector.h>
42#import <wtf/HashSet.h>
43
44#include <mach-o/dyld.h>
45
46static const int32_t webkitFirstVersionWithInitConstructorSupport = 0x21A0400; // 538.4.0
47
48@class JSObjCClassInfo;
49
50@interface JSWrapperMap ()
51
52- (JSObjCClassInfo*)classInfoForClass:(Class)cls;
53
54@end
55
56// Default conversion of selectors to property names.
57// All semicolons are removed, lowercase letters following a semicolon are capitalized.
58static NSString *selectorToPropertyName(const char* start)
59{
60    // Use 'index' to check for colons, if there are none, this is easy!
61    const char* firstColon = strchr(start, ':');
62    if (!firstColon)
63        return [NSString stringWithUTF8String:start];
64
65    // 'header' is the length of string up to the first colon.
66    size_t header = firstColon - start;
67    // The new string needs to be long enough to hold 'header', plus the remainder of the string, excluding
68    // at least one ':', but including a '\0'. (This is conservative if there are more than one ':').
69    char* buffer = static_cast<char*>(malloc(header + strlen(firstColon + 1) + 1));
70    // Copy 'header' characters, set output to point to the end of this & input to point past the first ':'.
71    memcpy(buffer, start, header);
72    char* output = buffer + header;
73    const char* input = start + header + 1;
74
75    // On entry to the loop, we have already skipped over a ':' from the input.
76    while (true) {
77        char c;
78        // Skip over any additional ':'s. We'll leave c holding the next character after the
79        // last ':', and input pointing past c.
80        while ((c = *(input++)) == ':');
81        // Copy the character, converting to upper case if necessary.
82        // If the character we copy is '\0', then we're done!
83        if (!(*(output++) = toupper(c)))
84            goto done;
85        // Loop over characters other than ':'.
86        while ((c = *(input++)) != ':') {
87            // Copy the character.
88            // If the character we copy is '\0', then we're done!
89            if (!(*(output++) = c))
90                goto done;
91        }
92        // If we get here, we've consumed a ':' - wash, rinse, repeat.
93    }
94done:
95    NSString *result = [NSString stringWithUTF8String:buffer];
96    free(buffer);
97    return result;
98}
99
100static bool constructorHasInstance(JSContextRef ctx, JSObjectRef constructorRef, JSValueRef possibleInstance, JSValueRef*)
101{
102    JSC::ExecState* exec = toJS(ctx);
103    JSC::JSLockHolder locker(exec);
104
105    JSC::JSObject* constructor = toJS(constructorRef);
106    JSC::JSValue instance = toJS(exec, possibleInstance);
107    return JSC::JSObject::defaultHasInstance(exec, instance, constructor->get(exec, exec->propertyNames().prototype));
108}
109
110static JSObjectRef makeWrapper(JSContextRef ctx, JSClassRef jsClass, id wrappedObject)
111{
112    JSC::ExecState* exec = toJS(ctx);
113    JSC::JSLockHolder locker(exec);
114
115    ASSERT(jsClass);
116    JSC::JSCallbackObject<JSC::JSAPIWrapperObject>* object = JSC::JSCallbackObject<JSC::JSAPIWrapperObject>::create(exec, exec->lexicalGlobalObject(), exec->lexicalGlobalObject()->objcWrapperObjectStructure(), jsClass, 0);
117    object->setWrappedObject(wrappedObject);
118    if (JSC::JSObject* prototype = jsClass->prototype(exec))
119        object->setPrototype(exec->vm(), prototype);
120
121    return toRef(object);
122}
123
124// Make an object that is in all ways a completely vanilla JavaScript object,
125// other than that it has a native brand set that will be displayed by the default
126// Object.prototype.toString conversion.
127static JSValue *objectWithCustomBrand(JSContext *context, NSString *brand, Class cls = 0)
128{
129    JSClassDefinition definition;
130    definition = kJSClassDefinitionEmpty;
131    definition.className = [brand UTF8String];
132    JSClassRef classRef = JSClassCreate(&definition);
133    JSObjectRef result = makeWrapper([context JSGlobalContextRef], classRef, cls);
134    JSClassRelease(classRef);
135    return [JSValue valueWithJSValueRef:result inContext:context];
136}
137
138static JSValue *constructorWithCustomBrand(JSContext *context, NSString *brand, Class cls)
139{
140    JSClassDefinition definition;
141    definition = kJSClassDefinitionEmpty;
142    definition.className = [brand UTF8String];
143    definition.hasInstance = constructorHasInstance;
144    JSClassRef classRef = JSClassCreate(&definition);
145    JSObjectRef result = makeWrapper([context JSGlobalContextRef], classRef, cls);
146    JSClassRelease(classRef);
147    return [JSValue valueWithJSValueRef:result inContext:context];
148}
149
150// Look for @optional properties in the prototype containing a selector to property
151// name mapping, separated by a __JS_EXPORT_AS__ delimiter.
152static NSMutableDictionary *createRenameMap(Protocol *protocol, BOOL isInstanceMethod)
153{
154    NSMutableDictionary *renameMap = [[NSMutableDictionary alloc] init];
155
156    forEachMethodInProtocol(protocol, NO, isInstanceMethod, ^(SEL sel, const char*){
157        NSString *rename = @(sel_getName(sel));
158        NSRange range = [rename rangeOfString:@"__JS_EXPORT_AS__"];
159        if (range.location == NSNotFound)
160            return;
161        NSString *selector = [rename substringToIndex:range.location];
162        NSUInteger begin = range.location + range.length;
163        NSUInteger length = [rename length] - begin - 1;
164        NSString *name = [rename substringWithRange:(NSRange){ begin, length }];
165        renameMap[selector] = name;
166    });
167
168    return renameMap;
169}
170
171inline void putNonEnumerable(JSValue *base, NSString *propertyName, JSValue *value)
172{
173    [base defineProperty:propertyName descriptor:@{
174        JSPropertyDescriptorValueKey: value,
175        JSPropertyDescriptorWritableKey: @YES,
176        JSPropertyDescriptorEnumerableKey: @NO,
177        JSPropertyDescriptorConfigurableKey: @YES
178    }];
179}
180
181static bool isInitFamilyMethod(NSString *name)
182{
183    NSUInteger i = 0;
184
185    // Skip over initial underscores.
186    for (; i < [name length]; ++i) {
187        if ([name characterAtIndex:i] != '_')
188            break;
189    }
190
191    // Match 'init'.
192    NSUInteger initIndex = 0;
193    NSString* init = @"init";
194    for (; i < [name length] && initIndex < [init length]; ++i, ++initIndex) {
195        if ([name characterAtIndex:i] != [init characterAtIndex:initIndex])
196            return false;
197    }
198
199    // We didn't match all of 'init'.
200    if (initIndex < [init length])
201        return false;
202
203    // If we're at the end or the next character is a capital letter then this is an init-family selector.
204    return i == [name length] || [[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember:[name characterAtIndex:i]];
205}
206
207static bool shouldSkipMethodWithName(NSString *name)
208{
209    // For clients that don't support init-based constructors just copy
210    // over the init method as we would have before.
211    if (!supportsInitMethodConstructors())
212        return false;
213
214    // Skip over init family methods because we handle those specially
215    // for the purposes of hooking up the constructor correctly.
216    return isInitFamilyMethod(name);
217}
218
219// This method will iterate over the set of required methods in the protocol, and:
220//  * Determine a property name (either via a renameMap or default conversion).
221//  * If an accessorMap is provided, and contains this name, store the method in the map.
222//  * Otherwise, if the object doesn't already contain a property with name, create it.
223static void copyMethodsToObject(JSContext *context, Class objcClass, Protocol *protocol, BOOL isInstanceMethod, JSValue *object, NSMutableDictionary *accessorMethods = nil)
224{
225    NSMutableDictionary *renameMap = createRenameMap(protocol, isInstanceMethod);
226
227    forEachMethodInProtocol(protocol, YES, isInstanceMethod, ^(SEL sel, const char* types){
228        const char* nameCStr = sel_getName(sel);
229        NSString *name = @(nameCStr);
230
231        if (shouldSkipMethodWithName(name))
232            return;
233
234        if (accessorMethods && accessorMethods[name]) {
235            JSObjectRef method = objCCallbackFunctionForMethod(context, objcClass, protocol, isInstanceMethod, sel, types);
236            if (!method)
237                return;
238            accessorMethods[name] = [JSValue valueWithJSValueRef:method inContext:context];
239        } else {
240            name = renameMap[name];
241            if (!name)
242                name = selectorToPropertyName(nameCStr);
243            if ([object hasProperty:name])
244                return;
245            JSObjectRef method = objCCallbackFunctionForMethod(context, objcClass, protocol, isInstanceMethod, sel, types);
246            if (method)
247                putNonEnumerable(object, name, [JSValue valueWithJSValueRef:method inContext:context]);
248        }
249    });
250
251    [renameMap release];
252}
253
254static bool parsePropertyAttributes(objc_property_t property, char*& getterName, char*& setterName)
255{
256    bool readonly = false;
257    unsigned attributeCount;
258    objc_property_attribute_t* attributes = property_copyAttributeList(property, &attributeCount);
259    if (attributeCount) {
260        for (unsigned i = 0; i < attributeCount; ++i) {
261            switch (*(attributes[i].name)) {
262            case 'G':
263                getterName = strdup(attributes[i].value);
264                break;
265            case 'S':
266                setterName = strdup(attributes[i].value);
267                break;
268            case 'R':
269                readonly = true;
270                break;
271            default:
272                break;
273            }
274        }
275        free(attributes);
276    }
277    return readonly;
278}
279
280static char* makeSetterName(const char* name)
281{
282    size_t nameLength = strlen(name);
283    char* setterName = (char*)malloc(nameLength + 5); // "set" Name ":\0"
284    setterName[0] = 's';
285    setterName[1] = 'e';
286    setterName[2] = 't';
287    setterName[3] = toupper(*name);
288    memcpy(setterName + 4, name + 1, nameLength - 1);
289    setterName[nameLength + 3] = ':';
290    setterName[nameLength + 4] = '\0';
291    return setterName;
292}
293
294static void copyPrototypeProperties(JSContext *context, Class objcClass, Protocol *protocol, JSValue *prototypeValue)
295{
296    // First gather propreties into this list, then handle the methods (capturing the accessor methods).
297    struct Property {
298        const char* name;
299        char* getterName;
300        char* setterName;
301    };
302    __block Vector<Property> propertyList;
303
304    // Map recording the methods used as getters/setters.
305    NSMutableDictionary *accessorMethods = [NSMutableDictionary dictionary];
306
307    // Useful value.
308    JSValue *undefined = [JSValue valueWithUndefinedInContext:context];
309
310    forEachPropertyInProtocol(protocol, ^(objc_property_t property){
311        char* getterName = 0;
312        char* setterName = 0;
313        bool readonly = parsePropertyAttributes(property, getterName, setterName);
314        const char* name = property_getName(property);
315
316        // Add the names of the getter & setter methods to
317        if (!getterName)
318            getterName = strdup(name);
319        accessorMethods[@(getterName)] = undefined;
320        if (!readonly) {
321            if (!setterName)
322                setterName = makeSetterName(name);
323            accessorMethods[@(setterName)] = undefined;
324        }
325
326        // Add the properties to a list.
327        propertyList.append((Property){ name, getterName, setterName });
328    });
329
330    // Copy methods to the prototype, capturing accessors in the accessorMethods map.
331    copyMethodsToObject(context, objcClass, protocol, YES, prototypeValue, accessorMethods);
332
333    // Iterate the propertyList & generate accessor properties.
334    for (size_t i = 0; i < propertyList.size(); ++i) {
335        Property& property = propertyList[i];
336
337        JSValue *getter = accessorMethods[@(property.getterName)];
338        free(property.getterName);
339        ASSERT(![getter isUndefined]);
340
341        JSValue *setter = undefined;
342        if (property.setterName) {
343            setter = accessorMethods[@(property.setterName)];
344            free(property.setterName);
345            ASSERT(![setter isUndefined]);
346        }
347
348        [prototypeValue defineProperty:@(property.name) descriptor:@{
349            JSPropertyDescriptorGetKey: getter,
350            JSPropertyDescriptorSetKey: setter,
351            JSPropertyDescriptorEnumerableKey: @NO,
352            JSPropertyDescriptorConfigurableKey: @YES
353        }];
354    }
355}
356
357@interface JSObjCClassInfo : NSObject {
358    JSContext *m_context;
359    Class m_class;
360    bool m_block;
361    JSClassRef m_classRef;
362    JSC::Weak<JSC::JSObject> m_prototype;
363    JSC::Weak<JSC::JSObject> m_constructor;
364}
365
366- (id)initWithContext:(JSContext *)context forClass:(Class)cls superClassInfo:(JSObjCClassInfo*)superClassInfo;
367- (JSValue *)wrapperForObject:(id)object;
368- (JSValue *)constructor;
369
370@end
371
372@implementation JSObjCClassInfo
373
374- (id)initWithContext:(JSContext *)context forClass:(Class)cls superClassInfo:(JSObjCClassInfo*)superClassInfo
375{
376    self = [super init];
377    if (!self)
378        return nil;
379
380    const char* className = class_getName(cls);
381    m_context = context;
382    m_class = cls;
383    m_block = [cls isSubclassOfClass:getNSBlockClass()];
384    JSClassDefinition definition;
385    definition = kJSClassDefinitionEmpty;
386    definition.className = className;
387    m_classRef = JSClassCreate(&definition);
388
389    [self allocateConstructorAndPrototypeWithSuperClassInfo:superClassInfo];
390
391    return self;
392}
393
394- (void)dealloc
395{
396    JSClassRelease(m_classRef);
397    [super dealloc];
398}
399
400static JSValue *allocateConstructorForCustomClass(JSContext *context, const char* className, Class cls)
401{
402    if (!supportsInitMethodConstructors())
403        return constructorWithCustomBrand(context, [NSString stringWithFormat:@"%sConstructor", className], cls);
404
405    // For each protocol that the class implements, gather all of the init family methods into a hash table.
406    __block HashMap<String, Protocol *> initTable;
407    Protocol *exportProtocol = getJSExportProtocol();
408    for (Class currentClass = cls; currentClass; currentClass = class_getSuperclass(currentClass)) {
409        forEachProtocolImplementingProtocol(currentClass, exportProtocol, ^(Protocol *protocol) {
410            forEachMethodInProtocol(protocol, YES, YES, ^(SEL selector, const char*) {
411                const char* name = sel_getName(selector);
412                if (!isInitFamilyMethod(@(name)))
413                    return;
414                initTable.set(name, protocol);
415            });
416        });
417    }
418
419    for (Class currentClass = cls; currentClass; currentClass = class_getSuperclass(currentClass)) {
420        __block unsigned numberOfInitsFound = 0;
421        __block SEL initMethod = 0;
422        __block Protocol *initProtocol = 0;
423        __block const char* types = 0;
424        forEachMethodInClass(currentClass, ^(Method method) {
425            SEL selector = method_getName(method);
426            const char* name = sel_getName(selector);
427            auto iter = initTable.find(name);
428
429            if (iter == initTable.end())
430                return;
431
432            numberOfInitsFound++;
433            initMethod = selector;
434            initProtocol = iter->value;
435            types = method_getTypeEncoding(method);
436        });
437
438        if (!numberOfInitsFound)
439            continue;
440
441        if (numberOfInitsFound > 1) {
442            NSLog(@"ERROR: Class %@ exported more than one init family method via JSExport. Class %@ will not have a callable JavaScript constructor function.", cls, cls);
443            break;
444        }
445
446        JSObjectRef method = objCCallbackFunctionForInit(context, cls, initProtocol, initMethod, types);
447        return [JSValue valueWithJSValueRef:method inContext:context];
448    }
449    return constructorWithCustomBrand(context, [NSString stringWithFormat:@"%sConstructor", className], cls);
450}
451
452- (void)allocateConstructorAndPrototypeWithSuperClassInfo:(JSObjCClassInfo*)superClassInfo
453{
454    ASSERT(!m_constructor || !m_prototype);
455    ASSERT((m_class == [NSObject class]) == !superClassInfo);
456    if (!superClassInfo) {
457        JSContextRef cContext = [m_context JSGlobalContextRef];
458        JSValue *constructor = m_context[@"Object"];
459        if (!m_constructor)
460            m_constructor = toJS(JSValueToObject(cContext, valueInternalValue(constructor), 0));
461
462        if (!m_prototype) {
463            JSValue *prototype = constructor[@"prototype"];
464            m_prototype = toJS(JSValueToObject(cContext, valueInternalValue(prototype), 0));
465        }
466    } else {
467        // We need to hold a reference to the superclass prototype here on the stack
468        // to that it won't get GC'ed while we do allocations between now and when we
469        // set it in this class' prototype below.
470        JSC::JSObject* superClassPrototype = superClassInfo->m_prototype.get();
471
472        const char* className = class_getName(m_class);
473
474        // Create or grab the prototype/constructor pair.
475        JSValue *prototype;
476        JSValue *constructor;
477        if (m_prototype)
478            prototype = [JSValue valueWithJSValueRef:toRef(m_prototype.get()) inContext:m_context];
479        else
480            prototype = objectWithCustomBrand(m_context, [NSString stringWithFormat:@"%sPrototype", className]);
481
482        if (m_constructor)
483            constructor = [JSValue valueWithJSValueRef:toRef(m_constructor.get()) inContext:m_context];
484        else
485            constructor = allocateConstructorForCustomClass(m_context, className, m_class);
486
487        JSContextRef cContext = [m_context JSGlobalContextRef];
488        m_prototype = toJS(JSValueToObject(cContext, valueInternalValue(prototype), 0));
489        m_constructor = toJS(JSValueToObject(cContext, valueInternalValue(constructor), 0));
490
491        putNonEnumerable(prototype, @"constructor", constructor);
492        putNonEnumerable(constructor, @"prototype", prototype);
493
494        Protocol *exportProtocol = getJSExportProtocol();
495        forEachProtocolImplementingProtocol(m_class, exportProtocol, ^(Protocol *protocol){
496            copyPrototypeProperties(m_context, m_class, protocol, prototype);
497            copyMethodsToObject(m_context, m_class, protocol, NO, constructor);
498        });
499
500        // Set [Prototype].
501        JSObjectSetPrototype([m_context JSGlobalContextRef], toRef(m_prototype.get()), toRef(superClassPrototype));
502    }
503}
504
505- (void)reallocateConstructorAndOrPrototype
506{
507    [self allocateConstructorAndPrototypeWithSuperClassInfo:[m_context.wrapperMap classInfoForClass:class_getSuperclass(m_class)]];
508    // We should not add any code here that can trigger a GC or the prototype and
509    // constructor that we just created may be collected before they can be used.
510}
511
512- (JSValue *)wrapperForObject:(id)object
513{
514    ASSERT([object isKindOfClass:m_class]);
515    ASSERT(m_block == [object isKindOfClass:getNSBlockClass()]);
516    if (m_block) {
517        if (JSObjectRef method = objCCallbackFunctionForBlock(m_context, object)) {
518            JSValue *constructor = [JSValue valueWithJSValueRef:method inContext:m_context];
519            JSValue *prototype = [JSValue valueWithNewObjectInContext:m_context];
520            putNonEnumerable(constructor, @"prototype", prototype);
521            putNonEnumerable(prototype, @"constructor", constructor);
522            return constructor;
523        }
524    }
525
526    if (!m_prototype)
527        [self reallocateConstructorAndOrPrototype];
528    ASSERT(!!m_prototype);
529    // We need to hold a reference to the prototype here on the stack to that it won't
530    // get GC'ed while we create the wrapper below.
531    JSC::JSObject* prototype = m_prototype.get();
532
533    JSObjectRef wrapper = makeWrapper([m_context JSGlobalContextRef], m_classRef, object);
534    JSObjectSetPrototype([m_context JSGlobalContextRef], wrapper, toRef(prototype));
535    return [JSValue valueWithJSValueRef:wrapper inContext:m_context];
536}
537
538- (JSValue *)constructor
539{
540    if (!m_constructor)
541        [self reallocateConstructorAndOrPrototype];
542    ASSERT(!!m_constructor);
543    // If we need to add any code here in the future that can trigger a GC, we should
544    // cache the constructor pointer in a stack local var first so that it is protected
545    // from the GC until it gets used below.
546    return [JSValue valueWithJSValueRef:toRef(m_constructor.get()) inContext:m_context];
547}
548
549@end
550
551@implementation JSWrapperMap {
552    JSContext *m_context;
553    NSMutableDictionary *m_classMap;
554    JSC::WeakGCMap<id, JSC::JSObject> m_cachedJSWrappers;
555    NSMapTable *m_cachedObjCWrappers;
556}
557
558- (id)initWithContext:(JSContext *)context
559{
560    self = [super init];
561    if (!self)
562        return nil;
563
564    NSPointerFunctionsOptions keyOptions = NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality;
565    NSPointerFunctionsOptions valueOptions = NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPersonality;
566    m_cachedObjCWrappers = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:valueOptions capacity:0];
567
568    m_context = context;
569    m_classMap = [[NSMutableDictionary alloc] init];
570    return self;
571}
572
573- (void)dealloc
574{
575    [m_cachedObjCWrappers release];
576    [m_classMap release];
577    [super dealloc];
578}
579
580- (JSObjCClassInfo*)classInfoForClass:(Class)cls
581{
582    if (!cls)
583        return nil;
584
585    // Check if we've already created a JSObjCClassInfo for this Class.
586    if (JSObjCClassInfo* classInfo = (JSObjCClassInfo*)m_classMap[cls])
587        return classInfo;
588
589    // Skip internal classes beginning with '_' - just copy link to the parent class's info.
590    if ('_' == *class_getName(cls))
591        return m_classMap[cls] = [self classInfoForClass:class_getSuperclass(cls)];
592
593    return m_classMap[cls] = [[[JSObjCClassInfo alloc] initWithContext:m_context forClass:cls superClassInfo:[self classInfoForClass:class_getSuperclass(cls)]] autorelease];
594}
595
596- (JSValue *)jsWrapperForObject:(id)object
597{
598    JSC::JSObject* jsWrapper = m_cachedJSWrappers.get(object);
599    if (jsWrapper)
600        return [JSValue valueWithJSValueRef:toRef(jsWrapper) inContext:m_context];
601
602    JSValue *wrapper;
603    if (class_isMetaClass(object_getClass(object)))
604        wrapper = [[self classInfoForClass:(Class)object] constructor];
605    else {
606        JSObjCClassInfo* classInfo = [self classInfoForClass:[object class]];
607        wrapper = [classInfo wrapperForObject:object];
608    }
609
610    // FIXME: https://bugs.webkit.org/show_bug.cgi?id=105891
611    // This general approach to wrapper caching is pretty effective, but there are a couple of problems:
612    // (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.
613    // (2) A long lived object may rack up many JSValues. When the contexts are released these will unprotect the associated JavaScript objects,
614    //     but still, would probably nicer if we made it so that only one associated object was required, broadcasting object dealloc.
615    JSC::ExecState* exec = toJS([m_context JSGlobalContextRef]);
616    jsWrapper = toJS(exec, valueInternalValue(wrapper)).toObject(exec);
617    m_cachedJSWrappers.set(object, jsWrapper);
618    return wrapper;
619}
620
621- (JSValue *)objcWrapperForJSValueRef:(JSValueRef)value
622{
623    JSValue *wrapper = static_cast<JSValue *>(NSMapGet(m_cachedObjCWrappers, value));
624    if (!wrapper) {
625        wrapper = [[[JSValue alloc] initWithValue:value inContext:m_context] autorelease];
626        NSMapInsert(m_cachedObjCWrappers, value, wrapper);
627    }
628    return wrapper;
629}
630
631@end
632
633id tryUnwrapObjcObject(JSGlobalContextRef context, JSValueRef value)
634{
635    if (!JSValueIsObject(context, value))
636        return nil;
637    JSValueRef exception = 0;
638    JSObjectRef object = JSValueToObject(context, value, &exception);
639    ASSERT(!exception);
640    JSC::JSLockHolder locker(toJS(context));
641    if (toJS(object)->inherits(JSC::JSCallbackObject<JSC::JSAPIWrapperObject>::info()))
642        return (id)JSC::jsCast<JSC::JSAPIWrapperObject*>(toJS(object))->wrappedObject();
643    if (id target = tryUnwrapConstructor(object))
644        return target;
645    return nil;
646}
647
648// This class ensures that the JSExport protocol is registered with the runtime.
649NS_ROOT_CLASS @interface JSExport <JSExport>
650@end
651@implementation JSExport
652@end
653
654bool supportsInitMethodConstructors()
655{
656    static int32_t versionOfLinkTimeLibrary = 0;
657    if (!versionOfLinkTimeLibrary)
658        versionOfLinkTimeLibrary = NSVersionOfLinkTimeLibrary("JavaScriptCore");
659    return versionOfLinkTimeLibrary >= webkitFirstVersionWithInitConstructorSupport;
660}
661
662Protocol *getJSExportProtocol()
663{
664    static Protocol *protocol = objc_getProtocol("JSExport");
665    return protocol;
666}
667
668Class getNSBlockClass()
669{
670    static Class cls = objc_getClass("NSBlock");
671    return cls;
672}
673
674#endif
675