1 /* 2 * Copyright (C) 2004 Apple Computer, 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 COMPUTER, 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 COMPUTER, 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 #include "objc_class.h" 28 29 #include "objc_instance.h" 30 #include "WebScriptObject.h" 31 32 namespace JSC { 33 namespace Bindings { 34 35 static void deleteMethod(CFAllocatorRef, const void* value) 36 { 37 delete static_cast<const Method*>(value); 38 } 39 40 static void deleteField(CFAllocatorRef, const void* value) 41 { 42 delete static_cast<const Field*>(value); 43 } 44 45 const CFDictionaryValueCallBacks MethodDictionaryValueCallBacks = { 0, 0, &deleteMethod, 0 , 0 }; 46 const CFDictionaryValueCallBacks FieldDictionaryValueCallBacks = { 0, 0, &deleteField, 0 , 0 }; 47 48 ObjcClass::ObjcClass(ClassStructPtr aClass) 49 : _isa(aClass) 50 , _methods(AdoptCF, CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &MethodDictionaryValueCallBacks)) 51 , _fields(AdoptCF, CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &FieldDictionaryValueCallBacks)) 52 { 53 } 54 55 static CFMutableDictionaryRef classesByIsA = 0; 56 57 static void _createClassesByIsAIfNecessary() 58 { 59 if (!classesByIsA) 60 classesByIsA = CFDictionaryCreateMutable(NULL, 0, NULL, NULL); 61 } 62 63 ObjcClass* ObjcClass::classForIsA(ClassStructPtr isa) 64 { 65 _createClassesByIsAIfNecessary(); 66 67 ObjcClass* aClass = (ObjcClass*)CFDictionaryGetValue(classesByIsA, isa); 68 if (!aClass) { 69 aClass = new ObjcClass(isa); 70 CFDictionaryAddValue(classesByIsA, isa, aClass); 71 } 72 73 return aClass; 74 } 75 76 MethodList ObjcClass::methodsNamed(const Identifier& identifier, Instance*) const 77 { 78 MethodList methodList; 79 char fixedSizeBuffer[1024]; 80 char* buffer = fixedSizeBuffer; 81 CString jsName = identifier.ascii(); 82 if (!convertJSMethodNameToObjc(jsName.data(), buffer, sizeof(fixedSizeBuffer))) { 83 int length = jsName.length() + 1; 84 buffer = new char[length]; 85 if (!buffer || !convertJSMethodNameToObjc(jsName.data(), buffer, length)) 86 return methodList; 87 } 88 89 90 RetainPtr<CFStringRef> methodName(AdoptCF, CFStringCreateWithCString(NULL, buffer, kCFStringEncodingASCII)); 91 Method* method = (Method*)CFDictionaryGetValue(_methods.get(), methodName.get()); 92 if (method) { 93 methodList.append(method); 94 return methodList; 95 } 96 97 ClassStructPtr thisClass = _isa; 98 while (thisClass && methodList.isEmpty()) { 99 #if defined(OBJC_API_VERSION) && OBJC_API_VERSION >= 2 100 unsigned numMethodsInClass = 0; 101 MethodStructPtr* objcMethodList = class_copyMethodList(thisClass, &numMethodsInClass); 102 #else 103 void* iterator = 0; 104 struct objc_method_list* objcMethodList; 105 while ((objcMethodList = class_nextMethodList(thisClass, &iterator))) { 106 unsigned numMethodsInClass = objcMethodList->method_count; 107 #endif 108 for (unsigned i = 0; i < numMethodsInClass; i++) { 109 #if defined(OBJC_API_VERSION) && OBJC_API_VERSION >= 2 110 MethodStructPtr objcMethod = objcMethodList[i]; 111 SEL objcMethodSelector = method_getName(objcMethod); 112 #else 113 struct objc_method* objcMethod = &objcMethodList->method_list[i]; 114 SEL objcMethodSelector = objcMethod->method_name; 115 #endif 116 const char* objcMethodSelectorName = sel_getName(objcMethodSelector); 117 NSString* mappedName = nil; 118 119 // See if the class wants to exclude the selector from visibility in JavaScript. 120 if ([thisClass respondsToSelector:@selector(isSelectorExcludedFromWebScript:)]) 121 if ([thisClass isSelectorExcludedFromWebScript:objcMethodSelector]) 122 continue; 123 124 // See if the class want to provide a different name for the selector in JavaScript. 125 // Note that we do not do any checks to guarantee uniqueness. That's the responsiblity 126 // of the class. 127 if ([thisClass respondsToSelector:@selector(webScriptNameForSelector:)]) 128 mappedName = [thisClass webScriptNameForSelector:objcMethodSelector]; 129 130 if ((mappedName && [mappedName isEqual:(NSString*)methodName.get()]) || strcmp(objcMethodSelectorName, buffer) == 0) { 131 Method* aMethod = new ObjcMethod(thisClass, objcMethodSelector); // deleted when the dictionary is destroyed 132 CFDictionaryAddValue(_methods.get(), methodName.get(), aMethod); 133 methodList.append(aMethod); 134 break; 135 } 136 } 137 #if defined(OBJC_API_VERSION) && OBJC_API_VERSION >= 2 138 thisClass = class_getSuperclass(thisClass); 139 free(objcMethodList); 140 #else 141 } 142 thisClass = thisClass->super_class; 143 #endif 144 } 145 146 if (buffer != fixedSizeBuffer) 147 delete [] buffer; 148 149 return methodList; 150 } 151 152 Field* ObjcClass::fieldNamed(const Identifier& identifier, Instance* instance) const 153 { 154 ClassStructPtr thisClass = _isa; 155 156 CString jsName = identifier.ascii(); 157 RetainPtr<CFStringRef> fieldName(AdoptCF, CFStringCreateWithCString(NULL, jsName.data(), kCFStringEncodingASCII)); 158 Field* aField = (Field*)CFDictionaryGetValue(_fields.get(), fieldName.get()); 159 if (aField) 160 return aField; 161 162 id targetObject = (static_cast<ObjcInstance*>(instance))->getObject(); 163 id attributes = [targetObject attributeKeys]; 164 if (attributes) { 165 // Class overrides attributeKeys, use that array of key names. 166 unsigned count = [attributes count]; 167 for (unsigned i = 0; i < count; i++) { 168 NSString* keyName = [attributes objectAtIndex:i]; 169 const char* UTF8KeyName = [keyName UTF8String]; // ObjC actually only supports ASCII names. 170 171 // See if the class wants to exclude the selector from visibility in JavaScript. 172 if ([thisClass respondsToSelector:@selector(isKeyExcludedFromWebScript:)]) 173 if ([thisClass isKeyExcludedFromWebScript:UTF8KeyName]) 174 continue; 175 176 // See if the class want to provide a different name for the selector in JavaScript. 177 // Note that we do not do any checks to guarantee uniqueness. That's the responsiblity 178 // of the class. 179 NSString* mappedName = nil; 180 if ([thisClass respondsToSelector:@selector(webScriptNameForKey:)]) 181 mappedName = [thisClass webScriptNameForKey:UTF8KeyName]; 182 183 if ((mappedName && [mappedName isEqual:(NSString*)fieldName.get()]) || [keyName isEqual:(NSString*)fieldName.get()]) { 184 aField = new ObjcField((CFStringRef)keyName); // deleted when the dictionary is destroyed 185 CFDictionaryAddValue(_fields.get(), fieldName.get(), aField); 186 break; 187 } 188 } 189 } else { 190 // Class doesn't override attributeKeys, so fall back on class runtime 191 // introspection. 192 193 while (thisClass) { 194 #if defined(OBJC_API_VERSION) && OBJC_API_VERSION >= 2 195 unsigned numFieldsInClass = 0; 196 IvarStructPtr* ivarsInClass = class_copyIvarList(thisClass, &numFieldsInClass); 197 #else 198 struct objc_ivar_list* fieldsInClass = thisClass->ivars; 199 if (fieldsInClass) { 200 unsigned numFieldsInClass = fieldsInClass->ivar_count; 201 #endif 202 for (unsigned i = 0; i < numFieldsInClass; i++) { 203 #if defined(OBJC_API_VERSION) && OBJC_API_VERSION >= 2 204 IvarStructPtr objcIVar = ivarsInClass[i]; 205 const char* objcIvarName = ivar_getName(objcIVar); 206 #else 207 IvarStructPtr objcIVar = &fieldsInClass->ivar_list[i]; 208 const char* objcIvarName = objcIVar->ivar_name; 209 #endif 210 NSString* mappedName = 0; 211 212 // See if the class wants to exclude the selector from visibility in JavaScript. 213 if ([thisClass respondsToSelector:@selector(isKeyExcludedFromWebScript:)]) 214 if ([thisClass isKeyExcludedFromWebScript:objcIvarName]) 215 continue; 216 217 // See if the class want to provide a different name for the selector in JavaScript. 218 // Note that we do not do any checks to guarantee uniqueness. That's the responsiblity 219 // of the class. 220 if ([thisClass respondsToSelector:@selector(webScriptNameForKey:)]) 221 mappedName = [thisClass webScriptNameForKey:objcIvarName]; 222 223 if ((mappedName && [mappedName isEqual:(NSString*)fieldName.get()]) || strcmp(objcIvarName, jsName.data()) == 0) { 224 aField = new ObjcField(objcIVar); // deleted when the dictionary is destroyed 225 CFDictionaryAddValue(_fields.get(), fieldName.get(), aField); 226 break; 227 } 228 } 229 #if defined(OBJC_API_VERSION) && OBJC_API_VERSION >= 2 230 thisClass = class_getSuperclass(thisClass); 231 free(ivarsInClass); 232 #else 233 } 234 thisClass = thisClass->super_class; 235 #endif 236 } 237 } 238 239 return aField; 240 } 241 242 JSValue ObjcClass::fallbackObject(ExecState* exec, Instance* instance, const Identifier &propertyName) 243 { 244 ObjcInstance* objcInstance = static_cast<ObjcInstance*>(instance); 245 id targetObject = objcInstance->getObject(); 246 247 if (![targetObject respondsToSelector:@selector(invokeUndefinedMethodFromWebScript:withArguments:)]) 248 return jsUndefined(); 249 return new (exec) ObjcFallbackObjectImp(exec, exec->lexicalGlobalObject(), objcInstance, propertyName); 250 } 251 252 } 253 } 254