JSWrapperMap.mm 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. /*
  2. * Copyright (C) 2013 Apple Inc. All rights reserved.
  3. *
  4. * Redistribution and use in source and binary forms, with or without
  5. * modification, are permitted provided that the following conditions
  6. * are met:
  7. * 1. Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * 2. Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. *
  13. * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
  14. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  15. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  16. * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
  17. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  18. * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  19. * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  20. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
  21. * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  22. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  23. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  24. */
  25. #include "config.h"
  26. #import "JavaScriptCore.h"
  27. #if JSC_OBJC_API_ENABLED
  28. #import "APICast.h"
  29. #import "APIShims.h"
  30. #import "JSAPIWrapperObject.h"
  31. #import "JSCallbackObject.h"
  32. #import "JSContextInternal.h"
  33. #import "JSWrapperMap.h"
  34. #import "ObjCCallbackFunction.h"
  35. #import "ObjcRuntimeExtras.h"
  36. #import "Operations.h"
  37. #import "WeakGCMap.h"
  38. #import <wtf/TCSpinLock.h>
  39. #import <wtf/Vector.h>
  40. @class JSObjCClassInfo;
  41. @interface JSWrapperMap ()
  42. - (JSObjCClassInfo*)classInfoForClass:(Class)cls;
  43. @end
  44. // Default conversion of selectors to property names.
  45. // All semicolons are removed, lowercase letters following a semicolon are capitalized.
  46. static NSString *selectorToPropertyName(const char* start)
  47. {
  48. // Use 'index' to check for colons, if there are none, this is easy!
  49. const char* firstColon = index(start, ':');
  50. if (!firstColon)
  51. return [NSString stringWithUTF8String:start];
  52. // 'header' is the length of string up to the first colon.
  53. size_t header = firstColon - start;
  54. // The new string needs to be long enough to hold 'header', plus the remainder of the string, excluding
  55. // at least one ':', but including a '\0'. (This is conservative if there are more than one ':').
  56. char* buffer = static_cast<char*>(malloc(header + strlen(firstColon + 1) + 1));
  57. // Copy 'header' characters, set output to point to the end of this & input to point past the first ':'.
  58. memcpy(buffer, start, header);
  59. char* output = buffer + header;
  60. const char* input = start + header + 1;
  61. // On entry to the loop, we have already skipped over a ':' from the input.
  62. while (true) {
  63. char c;
  64. // Skip over any additional ':'s. We'll leave c holding the next character after the
  65. // last ':', and input pointing past c.
  66. while ((c = *(input++)) == ':');
  67. // Copy the character, converting to upper case if necessary.
  68. // If the character we copy is '\0', then we're done!
  69. if (!(*(output++) = toupper(c)))
  70. goto done;
  71. // Loop over characters other than ':'.
  72. while ((c = *(input++)) != ':') {
  73. // Copy the character.
  74. // If the character we copy is '\0', then we're done!
  75. if (!(*(output++) = c))
  76. goto done;
  77. }
  78. // If we get here, we've consumed a ':' - wash, rinse, repeat.
  79. }
  80. done:
  81. NSString *result = [NSString stringWithUTF8String:buffer];
  82. free(buffer);
  83. return result;
  84. }
  85. static JSObjectRef makeWrapper(JSContextRef ctx, JSClassRef jsClass, id wrappedObject)
  86. {
  87. JSC::ExecState* exec = toJS(ctx);
  88. JSC::APIEntryShim entryShim(exec);
  89. ASSERT(jsClass);
  90. JSC::JSCallbackObject<JSC::JSAPIWrapperObject>* object = JSC::JSCallbackObject<JSC::JSAPIWrapperObject>::create(exec, exec->lexicalGlobalObject(), exec->lexicalGlobalObject()->objcWrapperObjectStructure(), jsClass, 0);
  91. object->setWrappedObject(wrappedObject);
  92. if (JSC::JSObject* prototype = jsClass->prototype(exec))
  93. object->setPrototype(exec->vm(), prototype);
  94. return toRef(object);
  95. }
  96. // Make an object that is in all ways a completely vanilla JavaScript object,
  97. // other than that it has a native brand set that will be displayed by the default
  98. // Object.prototype.toString conversion.
  99. static JSValue *objectWithCustomBrand(JSContext *context, NSString *brand, Class cls = 0)
  100. {
  101. JSClassDefinition definition;
  102. definition = kJSClassDefinitionEmpty;
  103. definition.className = [brand UTF8String];
  104. JSClassRef classRef = JSClassCreate(&definition);
  105. JSObjectRef result = makeWrapper([context JSGlobalContextRef], classRef, cls);
  106. JSClassRelease(classRef);
  107. return [JSValue valueWithJSValueRef:result inContext:context];
  108. }
  109. // Look for @optional properties in the prototype containing a selector to property
  110. // name mapping, separated by a __JS_EXPORT_AS__ delimiter.
  111. static NSMutableDictionary *createRenameMap(Protocol *protocol, BOOL isInstanceMethod)
  112. {
  113. NSMutableDictionary *renameMap = [[NSMutableDictionary alloc] init];
  114. forEachMethodInProtocol(protocol, NO, isInstanceMethod, ^(SEL sel, const char*){
  115. NSString *rename = @(sel_getName(sel));
  116. NSRange range = [rename rangeOfString:@"__JS_EXPORT_AS__"];
  117. if (range.location == NSNotFound)
  118. return;
  119. NSString *selector = [rename substringToIndex:range.location];
  120. NSUInteger begin = range.location + range.length;
  121. NSUInteger length = [rename length] - begin - 1;
  122. NSString *name = [rename substringWithRange:(NSRange){ begin, length }];
  123. renameMap[selector] = name;
  124. });
  125. return renameMap;
  126. }
  127. inline void putNonEnumerable(JSValue *base, NSString *propertyName, JSValue *value)
  128. {
  129. [base defineProperty:propertyName descriptor:@{
  130. JSPropertyDescriptorValueKey: value,
  131. JSPropertyDescriptorWritableKey: @YES,
  132. JSPropertyDescriptorEnumerableKey: @NO,
  133. JSPropertyDescriptorConfigurableKey: @YES
  134. }];
  135. }
  136. // This method will iterate over the set of required methods in the protocol, and:
  137. // * Determine a property name (either via a renameMap or default conversion).
  138. // * If an accessorMap is provided, and contains this name, store the method in the map.
  139. // * Otherwise, if the object doesn't already contain a property with name, create it.
  140. static void copyMethodsToObject(JSContext *context, Class objcClass, Protocol *protocol, BOOL isInstanceMethod, JSValue *object, NSMutableDictionary *accessorMethods = nil)
  141. {
  142. NSMutableDictionary *renameMap = createRenameMap(protocol, isInstanceMethod);
  143. forEachMethodInProtocol(protocol, YES, isInstanceMethod, ^(SEL sel, const char* types){
  144. const char* nameCStr = sel_getName(sel);
  145. NSString *name = @(nameCStr);
  146. if (accessorMethods && accessorMethods[name]) {
  147. JSObjectRef method = objCCallbackFunctionForMethod(context, objcClass, protocol, isInstanceMethod, sel, types);
  148. if (!method)
  149. return;
  150. accessorMethods[name] = [JSValue valueWithJSValueRef:method inContext:context];
  151. } else {
  152. name = renameMap[name];
  153. if (!name)
  154. name = selectorToPropertyName(nameCStr);
  155. if ([object hasProperty:name])
  156. return;
  157. JSObjectRef method = objCCallbackFunctionForMethod(context, objcClass, protocol, isInstanceMethod, sel, types);
  158. if (method)
  159. putNonEnumerable(object, name, [JSValue valueWithJSValueRef:method inContext:context]);
  160. }
  161. });
  162. [renameMap release];
  163. }
  164. static bool parsePropertyAttributes(objc_property_t property, char*& getterName, char*& setterName)
  165. {
  166. bool readonly = false;
  167. unsigned attributeCount;
  168. objc_property_attribute_t* attributes = property_copyAttributeList(property, &attributeCount);
  169. if (attributeCount) {
  170. for (unsigned i = 0; i < attributeCount; ++i) {
  171. switch (*(attributes[i].name)) {
  172. case 'G':
  173. getterName = strdup(attributes[i].value);
  174. break;
  175. case 'S':
  176. setterName = strdup(attributes[i].value);
  177. break;
  178. case 'R':
  179. readonly = true;
  180. break;
  181. default:
  182. break;
  183. }
  184. }
  185. free(attributes);
  186. }
  187. return readonly;
  188. }
  189. static char* makeSetterName(const char* name)
  190. {
  191. size_t nameLength = strlen(name);
  192. char* setterName = (char*)malloc(nameLength + 5); // "set" Name ":\0"
  193. setterName[0] = 's';
  194. setterName[1] = 'e';
  195. setterName[2] = 't';
  196. setterName[3] = toupper(*name);
  197. memcpy(setterName + 4, name + 1, nameLength - 1);
  198. setterName[nameLength + 3] = ':';
  199. setterName[nameLength + 4] = '\0';
  200. return setterName;
  201. }
  202. static void copyPrototypeProperties(JSContext *context, Class objcClass, Protocol *protocol, JSValue *prototypeValue)
  203. {
  204. // First gather propreties into this list, then handle the methods (capturing the accessor methods).
  205. struct Property {
  206. const char* name;
  207. char* getterName;
  208. char* setterName;
  209. };
  210. __block Vector<Property> propertyList;
  211. // Map recording the methods used as getters/setters.
  212. NSMutableDictionary *accessorMethods = [NSMutableDictionary dictionary];
  213. // Useful value.
  214. JSValue *undefined = [JSValue valueWithUndefinedInContext:context];
  215. forEachPropertyInProtocol(protocol, ^(objc_property_t property){
  216. char* getterName = 0;
  217. char* setterName = 0;
  218. bool readonly = parsePropertyAttributes(property, getterName, setterName);
  219. const char* name = property_getName(property);
  220. // Add the names of the getter & setter methods to
  221. if (!getterName)
  222. getterName = strdup(name);
  223. accessorMethods[@(getterName)] = undefined;
  224. if (!readonly) {
  225. if (!setterName)
  226. setterName = makeSetterName(name);
  227. accessorMethods[@(setterName)] = undefined;
  228. }
  229. // Add the properties to a list.
  230. propertyList.append((Property){ name, getterName, setterName });
  231. });
  232. // Copy methods to the prototype, capturing accessors in the accessorMethods map.
  233. copyMethodsToObject(context, objcClass, protocol, YES, prototypeValue, accessorMethods);
  234. // Iterate the propertyList & generate accessor properties.
  235. for (size_t i = 0; i < propertyList.size(); ++i) {
  236. Property& property = propertyList[i];
  237. JSValue *getter = accessorMethods[@(property.getterName)];
  238. free(property.getterName);
  239. ASSERT(![getter isUndefined]);
  240. JSValue *setter = undefined;
  241. if (property.setterName) {
  242. setter = accessorMethods[@(property.setterName)];
  243. free(property.setterName);
  244. ASSERT(![setter isUndefined]);
  245. }
  246. [prototypeValue defineProperty:@(property.name) descriptor:@{
  247. JSPropertyDescriptorGetKey: getter,
  248. JSPropertyDescriptorSetKey: setter,
  249. JSPropertyDescriptorEnumerableKey: @NO,
  250. JSPropertyDescriptorConfigurableKey: @YES
  251. }];
  252. }
  253. }
  254. @interface JSObjCClassInfo : NSObject {
  255. JSContext *m_context;
  256. Class m_class;
  257. bool m_block;
  258. JSClassRef m_classRef;
  259. JSC::Weak<JSC::JSObject> m_prototype;
  260. JSC::Weak<JSC::JSObject> m_constructor;
  261. }
  262. - (id)initWithContext:(JSContext *)context forClass:(Class)cls superClassInfo:(JSObjCClassInfo*)superClassInfo;
  263. - (JSValue *)wrapperForObject:(id)object;
  264. - (JSValue *)constructor;
  265. @end
  266. @implementation JSObjCClassInfo
  267. - (id)initWithContext:(JSContext *)context forClass:(Class)cls superClassInfo:(JSObjCClassInfo*)superClassInfo
  268. {
  269. self = [super init];
  270. if (!self)
  271. return nil;
  272. const char* className = class_getName(cls);
  273. m_context = context;
  274. m_class = cls;
  275. m_block = [cls isSubclassOfClass:getNSBlockClass()];
  276. JSClassDefinition definition;
  277. definition = kJSClassDefinitionEmpty;
  278. definition.className = className;
  279. m_classRef = JSClassCreate(&definition);
  280. [self allocateConstructorAndPrototypeWithSuperClassInfo:superClassInfo];
  281. return self;
  282. }
  283. - (void)dealloc
  284. {
  285. JSClassRelease(m_classRef);
  286. [super dealloc];
  287. }
  288. - (void)allocateConstructorAndPrototypeWithSuperClassInfo:(JSObjCClassInfo*)superClassInfo
  289. {
  290. ASSERT(!m_constructor || !m_prototype);
  291. ASSERT((m_class == [NSObject class]) == !superClassInfo);
  292. if (!superClassInfo) {
  293. JSContextRef cContext = [m_context JSGlobalContextRef];
  294. JSValue *constructor = m_context[@"Object"];
  295. if (!m_constructor)
  296. m_constructor = toJS(JSValueToObject(cContext, valueInternalValue(constructor), 0));
  297. if (!m_prototype) {
  298. JSValue *prototype = constructor[@"prototype"];
  299. m_prototype = toJS(JSValueToObject(cContext, valueInternalValue(prototype), 0));
  300. }
  301. } else {
  302. const char* className = class_getName(m_class);
  303. // Create or grab the prototype/constructor pair.
  304. JSValue *prototype;
  305. JSValue *constructor;
  306. if (m_prototype)
  307. prototype = [JSValue valueWithJSValueRef:toRef(m_prototype.get()) inContext:m_context];
  308. else
  309. prototype = objectWithCustomBrand(m_context, [NSString stringWithFormat:@"%sPrototype", className]);
  310. if (m_constructor)
  311. constructor = [JSValue valueWithJSValueRef:toRef(m_constructor.get()) inContext:m_context];
  312. else
  313. constructor = objectWithCustomBrand(m_context, [NSString stringWithFormat:@"%sConstructor", className], m_class);
  314. JSContextRef cContext = [m_context JSGlobalContextRef];
  315. m_prototype = toJS(JSValueToObject(cContext, valueInternalValue(prototype), 0));
  316. m_constructor = toJS(JSValueToObject(cContext, valueInternalValue(constructor), 0));
  317. putNonEnumerable(prototype, @"constructor", constructor);
  318. putNonEnumerable(constructor, @"prototype", prototype);
  319. Protocol *exportProtocol = getJSExportProtocol();
  320. forEachProtocolImplementingProtocol(m_class, exportProtocol, ^(Protocol *protocol){
  321. copyPrototypeProperties(m_context, m_class, protocol, prototype);
  322. copyMethodsToObject(m_context, m_class, protocol, NO, constructor);
  323. });
  324. // Set [Prototype].
  325. JSObjectSetPrototype([m_context JSGlobalContextRef], toRef(m_prototype.get()), toRef(superClassInfo->m_prototype.get()));
  326. }
  327. }
  328. - (void)reallocateConstructorAndOrPrototype
  329. {
  330. [self allocateConstructorAndPrototypeWithSuperClassInfo:[m_context.wrapperMap classInfoForClass:class_getSuperclass(m_class)]];
  331. }
  332. - (JSValue *)wrapperForObject:(id)object
  333. {
  334. ASSERT([object isKindOfClass:m_class]);
  335. ASSERT(m_block == [object isKindOfClass:getNSBlockClass()]);
  336. if (m_block) {
  337. if (JSObjectRef method = objCCallbackFunctionForBlock(m_context, object))
  338. return [JSValue valueWithJSValueRef:method inContext:m_context];
  339. }
  340. if (!m_prototype)
  341. [self reallocateConstructorAndOrPrototype];
  342. ASSERT(!!m_prototype);
  343. JSObjectRef wrapper = makeWrapper([m_context JSGlobalContextRef], m_classRef, object);
  344. JSObjectSetPrototype([m_context JSGlobalContextRef], wrapper, toRef(m_prototype.get()));
  345. return [JSValue valueWithJSValueRef:wrapper inContext:m_context];
  346. }
  347. - (JSValue *)constructor
  348. {
  349. if (!m_constructor)
  350. [self reallocateConstructorAndOrPrototype];
  351. ASSERT(!!m_constructor);
  352. return [JSValue valueWithJSValueRef:toRef(m_constructor.get()) inContext:m_context];
  353. }
  354. @end
  355. @implementation JSWrapperMap {
  356. JSContext *m_context;
  357. NSMutableDictionary *m_classMap;
  358. JSC::WeakGCMap<id, JSC::JSObject> m_cachedJSWrappers;
  359. NSMapTable *m_cachedObjCWrappers;
  360. }
  361. - (id)initWithContext:(JSContext *)context
  362. {
  363. self = [super init];
  364. if (!self)
  365. return nil;
  366. NSPointerFunctionsOptions keyOptions = NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality;
  367. NSPointerFunctionsOptions valueOptions = NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPersonality;
  368. m_cachedObjCWrappers = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:valueOptions capacity:0];
  369. m_context = context;
  370. m_classMap = [[NSMutableDictionary alloc] init];
  371. return self;
  372. }
  373. - (void)dealloc
  374. {
  375. [m_cachedObjCWrappers release];
  376. [m_classMap release];
  377. [super dealloc];
  378. }
  379. - (JSObjCClassInfo*)classInfoForClass:(Class)cls
  380. {
  381. if (!cls)
  382. return nil;
  383. // Check if we've already created a JSObjCClassInfo for this Class.
  384. if (JSObjCClassInfo* classInfo = (JSObjCClassInfo*)m_classMap[cls])
  385. return classInfo;
  386. // Skip internal classes beginning with '_' - just copy link to the parent class's info.
  387. if ('_' == *class_getName(cls))
  388. return m_classMap[cls] = [self classInfoForClass:class_getSuperclass(cls)];
  389. return m_classMap[cls] = [[[JSObjCClassInfo alloc] initWithContext:m_context forClass:cls superClassInfo:[self classInfoForClass:class_getSuperclass(cls)]] autorelease];
  390. }
  391. - (JSValue *)jsWrapperForObject:(id)object
  392. {
  393. JSC::JSObject* jsWrapper = m_cachedJSWrappers.get(object);
  394. if (jsWrapper)
  395. return [JSValue valueWithJSValueRef:toRef(jsWrapper) inContext:m_context];
  396. JSValue *wrapper;
  397. if (class_isMetaClass(object_getClass(object)))
  398. wrapper = [[self classInfoForClass:(Class)object] constructor];
  399. else {
  400. JSObjCClassInfo* classInfo = [self classInfoForClass:[object class]];
  401. wrapper = [classInfo wrapperForObject:object];
  402. }
  403. // FIXME: https://bugs.webkit.org/show_bug.cgi?id=105891
  404. // This general approach to wrapper caching is pretty effective, but there are a couple of problems:
  405. // (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.
  406. // (2) A long lived object may rack up many JSValues. When the contexts are released these will unprotect the associated JavaScript objects,
  407. // but still, would probably nicer if we made it so that only one associated object was required, broadcasting object dealloc.
  408. JSC::ExecState* exec = toJS([m_context JSGlobalContextRef]);
  409. jsWrapper = toJS(exec, valueInternalValue(wrapper)).toObject(exec);
  410. m_cachedJSWrappers.set(object, jsWrapper);
  411. return wrapper;
  412. }
  413. - (JSValue *)objcWrapperForJSValueRef:(JSValueRef)value
  414. {
  415. JSValue *wrapper = static_cast<JSValue *>(NSMapGet(m_cachedObjCWrappers, value));
  416. if (!wrapper) {
  417. wrapper = [[[JSValue alloc] initWithValue:value inContext:m_context] autorelease];
  418. NSMapInsert(m_cachedObjCWrappers, value, wrapper);
  419. }
  420. return wrapper;
  421. }
  422. @end
  423. id tryUnwrapObjcObject(JSGlobalContextRef context, JSValueRef value)
  424. {
  425. if (!JSValueIsObject(context, value))
  426. return nil;
  427. JSValueRef exception = 0;
  428. JSObjectRef object = JSValueToObject(context, value, &exception);
  429. ASSERT(!exception);
  430. if (toJS(object)->inherits(&JSC::JSCallbackObject<JSC::JSAPIWrapperObject>::s_info))
  431. return (id)JSC::jsCast<JSC::JSAPIWrapperObject*>(toJS(object))->wrappedObject();
  432. if (id target = tryUnwrapBlock(object))
  433. return target;
  434. return nil;
  435. }
  436. Protocol *getJSExportProtocol()
  437. {
  438. static Protocol *protocol = objc_getProtocol("JSExport");
  439. return protocol;
  440. }
  441. Class getNSBlockClass()
  442. {
  443. static Class cls = objc_getClass("NSBlock");
  444. return cls;
  445. }
  446. #endif