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