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