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