1/* 2 * Copyright (C) 2004, 2008, 2009 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 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#import "config.h" 27#import "objc_instance.h" 28 29#import "FoundationExtras.h" 30#import "WebScriptObject.h" 31#import <objc/objc-auto.h> 32#import <runtime/Error.h> 33#import <runtime/JSLock.h> 34#import <wtf/Assertions.h> 35 36#ifdef NDEBUG 37#define OBJC_LOG(formatAndArgs...) ((void)0) 38#else 39#define OBJC_LOG(formatAndArgs...) { \ 40 fprintf (stderr, "%s:%d -- %s: ", __FILE__, __LINE__, __FUNCTION__); \ 41 fprintf(stderr, formatAndArgs); \ 42} 43#endif 44 45using namespace JSC::Bindings; 46using namespace JSC; 47 48static NSString *s_exception; 49static JSGlobalObject* s_exceptionEnvironment; // No need to protect this value, since we just use it for a pointer comparison. 50static NSMapTable *s_instanceWrapperCache; 51 52static NSMapTable *createInstanceWrapperCache() 53{ 54#ifdef BUILDING_ON_TIGER 55 return NSCreateMapTable(NSNonRetainedObjectMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0); 56#else 57 // NSMapTable with zeroing weak pointers is the recommended way to build caches like this under garbage collection. 58 NSPointerFunctionsOptions keyOptions = NSPointerFunctionsZeroingWeakMemory | NSPointerFunctionsObjectPersonality; 59 NSPointerFunctionsOptions valueOptions = NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality; 60 return [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:valueOptions capacity:0]; 61#endif 62} 63 64void ObjcInstance::setGlobalException(NSString* exception, JSGlobalObject* exceptionEnvironment) 65{ 66 HardRelease(s_exception); 67 HardRetain(exception); 68 s_exception = exception; 69 70 s_exceptionEnvironment = exceptionEnvironment; 71} 72 73void ObjcInstance::moveGlobalExceptionToExecState(ExecState* exec) 74{ 75 if (!s_exception) { 76 ASSERT(!s_exceptionEnvironment); 77 return; 78 } 79 80 if (!s_exceptionEnvironment || s_exceptionEnvironment == exec->dynamicGlobalObject()) { 81 JSLock lock(SilenceAssertionsOnly); 82 throwError(exec, GeneralError, s_exception); 83 } 84 85 HardRelease(s_exception); 86 s_exception = 0; 87 88 s_exceptionEnvironment = 0; 89} 90 91ObjcInstance::ObjcInstance(id instance, PassRefPtr<RootObject> rootObject) 92 : Instance(rootObject) 93 , _instance(instance) 94 , _class(0) 95 , _pool(0) 96 , _beginCount(0) 97{ 98} 99 100PassRefPtr<ObjcInstance> ObjcInstance::create(id instance, PassRefPtr<RootObject> rootObject) 101{ 102 if (!s_instanceWrapperCache) 103 s_instanceWrapperCache = createInstanceWrapperCache(); 104 if (void* existingWrapper = NSMapGet(s_instanceWrapperCache, instance)) 105 return static_cast<ObjcInstance*>(existingWrapper); 106 RefPtr<ObjcInstance> wrapper = adoptRef(new ObjcInstance(instance, rootObject)); 107 NSMapInsert(s_instanceWrapperCache, instance, wrapper.get()); 108 return wrapper.release(); 109} 110 111ObjcInstance::~ObjcInstance() 112{ 113 // Both -finalizeForWebScript and -dealloc/-finalize of _instance may require autorelease pools. 114 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; 115 116 ASSERT(s_instanceWrapperCache); 117 ASSERT(_instance); 118 NSMapRemove(s_instanceWrapperCache, _instance.get()); 119 120 if ([_instance.get() respondsToSelector:@selector(finalizeForWebScript)]) 121 [_instance.get() performSelector:@selector(finalizeForWebScript)]; 122 _instance = 0; 123 124 [pool drain]; 125} 126 127static NSAutoreleasePool* allocateAutoReleasePool() 128{ 129#if defined(OBJC_API_VERSION) && OBJC_API_VERSION >= 2 130 // If GC is enabled an autorelease pool is unnecessary, and the 131 // pool cannot be protected from GC so may be collected leading 132 // to a crash when we try to drain the release pool. 133 if (objc_collectingEnabled()) 134 return nil; 135#endif 136 return [[NSAutoreleasePool alloc] init]; 137} 138 139void ObjcInstance::virtualBegin() 140{ 141 if (!_pool) 142 _pool = allocateAutoReleasePool(); 143 _beginCount++; 144} 145 146void ObjcInstance::virtualEnd() 147{ 148 _beginCount--; 149 ASSERT(_beginCount >= 0); 150 if (!_beginCount) { 151 [_pool drain]; 152 _pool = 0; 153 } 154} 155 156Bindings::Class* ObjcInstance::getClass() const 157{ 158 if (!_instance) 159 return 0; 160 if (!_class) 161 _class = ObjcClass::classForIsA(_instance->isa); 162 return static_cast<Bindings::Class*>(_class); 163} 164 165bool ObjcInstance::supportsInvokeDefaultMethod() const 166{ 167 return [_instance.get() respondsToSelector:@selector(invokeDefaultMethodWithArguments:)]; 168} 169 170JSValue ObjcInstance::invokeMethod(ExecState* exec, const MethodList &methodList, const ArgList &args) 171{ 172 JSValue result = jsUndefined(); 173 174 JSLock::DropAllLocks dropAllLocks(SilenceAssertionsOnly); // Can't put this inside the @try scope because it unwinds incorrectly. 175 176 setGlobalException(nil); 177 178 // Overloading methods is not allowed in ObjectiveC. Should only be one 179 // name match for a particular method. 180 ASSERT(methodList.size() == 1); 181 182@try { 183 ObjcMethod* method = 0; 184 method = static_cast<ObjcMethod*>(methodList[0]); 185 NSMethodSignature* signature = method->getMethodSignature(); 186 NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:signature]; 187 [invocation setSelector:method->selector()]; 188 [invocation setTarget:_instance.get()]; 189 190 if (method->isFallbackMethod()) { 191 if (objcValueTypeForType([signature methodReturnType]) != ObjcObjectType) { 192 NSLog(@"Incorrect signature for invokeUndefinedMethodFromWebScript:withArguments: -- return type must be object."); 193 return result; 194 } 195 196 // Invoke invokeUndefinedMethodFromWebScript:withArguments:, pass JavaScript function 197 // name as first (actually at 2) argument and array of args as second. 198 NSString* jsName = (NSString* )method->javaScriptName(); 199 [invocation setArgument:&jsName atIndex:2]; 200 201 NSMutableArray* objcArgs = [NSMutableArray array]; 202 int count = args.size(); 203 for (int i = 0; i < count; i++) { 204 ObjcValue value = convertValueToObjcValue(exec, args.at(i), ObjcObjectType); 205 [objcArgs addObject:value.objectValue]; 206 } 207 [invocation setArgument:&objcArgs atIndex:3]; 208 } else { 209 unsigned count = [signature numberOfArguments]; 210 for (unsigned i = 2; i < count ; i++) { 211 const char* type = [signature getArgumentTypeAtIndex:i]; 212 ObjcValueType objcValueType = objcValueTypeForType(type); 213 214 // Must have a valid argument type. This method signature should have 215 // been filtered already to ensure that it has acceptable argument 216 // types. 217 ASSERT(objcValueType != ObjcInvalidType && objcValueType != ObjcVoidType); 218 219 ObjcValue value = convertValueToObjcValue(exec, args.at(i-2), objcValueType); 220 221 switch (objcValueType) { 222 case ObjcObjectType: 223 [invocation setArgument:&value.objectValue atIndex:i]; 224 break; 225 case ObjcCharType: 226 case ObjcUnsignedCharType: 227 [invocation setArgument:&value.charValue atIndex:i]; 228 break; 229 case ObjcShortType: 230 case ObjcUnsignedShortType: 231 [invocation setArgument:&value.shortValue atIndex:i]; 232 break; 233 case ObjcIntType: 234 case ObjcUnsignedIntType: 235 [invocation setArgument:&value.intValue atIndex:i]; 236 break; 237 case ObjcLongType: 238 case ObjcUnsignedLongType: 239 [invocation setArgument:&value.longValue atIndex:i]; 240 break; 241 case ObjcLongLongType: 242 case ObjcUnsignedLongLongType: 243 [invocation setArgument:&value.longLongValue atIndex:i]; 244 break; 245 case ObjcFloatType: 246 [invocation setArgument:&value.floatValue atIndex:i]; 247 break; 248 case ObjcDoubleType: 249 [invocation setArgument:&value.doubleValue atIndex:i]; 250 break; 251 default: 252 // Should never get here. Argument types are filtered (and 253 // the assert above should have fired in the impossible case 254 // of an invalid type anyway). 255 fprintf(stderr, "%s: invalid type (%d)\n", __PRETTY_FUNCTION__, (int)objcValueType); 256 ASSERT(false); 257 } 258 } 259 } 260 261 [invocation invoke]; 262 263 // Get the return value type. 264 const char* type = [signature methodReturnType]; 265 ObjcValueType objcValueType = objcValueTypeForType(type); 266 267 // Must have a valid return type. This method signature should have 268 // been filtered already to ensure that it have an acceptable return 269 // type. 270 ASSERT(objcValueType != ObjcInvalidType); 271 272 // Get the return value and convert it to a JavaScript value. Length 273 // of return value will never exceed the size of largest scalar 274 // or a pointer. 275 char buffer[1024]; 276 ASSERT([signature methodReturnLength] < 1024); 277 278 if (*type != 'v') { 279 [invocation getReturnValue:buffer]; 280 result = convertObjcValueToValue(exec, buffer, objcValueType, m_rootObject.get()); 281 } 282} @catch(NSException* localException) { 283} 284 moveGlobalExceptionToExecState(exec); 285 286 // Work around problem in some versions of GCC where result gets marked volatile and 287 // it can't handle copying from a volatile to non-volatile. 288 return const_cast<JSValue&>(result); 289} 290 291JSValue ObjcInstance::invokeDefaultMethod(ExecState* exec, const ArgList &args) 292{ 293 JSValue result = jsUndefined(); 294 295 JSLock::DropAllLocks dropAllLocks(SilenceAssertionsOnly); // Can't put this inside the @try scope because it unwinds incorrectly. 296 setGlobalException(nil); 297 298@try { 299 if (![_instance.get() respondsToSelector:@selector(invokeDefaultMethodWithArguments:)]) 300 return result; 301 302 NSMethodSignature* signature = [_instance.get() methodSignatureForSelector:@selector(invokeDefaultMethodWithArguments:)]; 303 NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:signature]; 304 [invocation setSelector:@selector(invokeDefaultMethodWithArguments:)]; 305 [invocation setTarget:_instance.get()]; 306 307 if (objcValueTypeForType([signature methodReturnType]) != ObjcObjectType) { 308 NSLog(@"Incorrect signature for invokeDefaultMethodWithArguments: -- return type must be object."); 309 return result; 310 } 311 312 NSMutableArray* objcArgs = [NSMutableArray array]; 313 unsigned count = args.size(); 314 for (unsigned i = 0; i < count; i++) { 315 ObjcValue value = convertValueToObjcValue(exec, args.at(i), ObjcObjectType); 316 [objcArgs addObject:value.objectValue]; 317 } 318 [invocation setArgument:&objcArgs atIndex:2]; 319 320 [invocation invoke]; 321 322 // Get the return value type, should always be "@" because of 323 // check above. 324 const char* type = [signature methodReturnType]; 325 ObjcValueType objcValueType = objcValueTypeForType(type); 326 327 // Get the return value and convert it to a JavaScript value. Length 328 // of return value will never exceed the size of a pointer, so we're 329 // OK with 32 here. 330 char buffer[32]; 331 [invocation getReturnValue:buffer]; 332 result = convertObjcValueToValue(exec, buffer, objcValueType, m_rootObject.get()); 333} @catch(NSException* localException) { 334} 335 moveGlobalExceptionToExecState(exec); 336 337 // Work around problem in some versions of GCC where result gets marked volatile and 338 // it can't handle copying from a volatile to non-volatile. 339 return const_cast<JSValue&>(result); 340} 341 342bool ObjcInstance::setValueOfUndefinedField(ExecState* exec, const Identifier& property, JSValue aValue) 343{ 344 id targetObject = getObject(); 345 if (![targetObject respondsToSelector:@selector(setValue:forUndefinedKey:)]) 346 return false; 347 348 JSLock::DropAllLocks dropAllLocks(SilenceAssertionsOnly); // Can't put this inside the @try scope because it unwinds incorrectly. 349 350 // This check is not really necessary because NSObject implements 351 // setValue:forUndefinedKey:, and unfortunately the default implementation 352 // throws an exception. 353 if ([targetObject respondsToSelector:@selector(setValue:forUndefinedKey:)]){ 354 setGlobalException(nil); 355 356 ObjcValue objcValue = convertValueToObjcValue(exec, aValue, ObjcObjectType); 357 358 @try { 359 [targetObject setValue:objcValue.objectValue forUndefinedKey:[NSString stringWithCString:property.ascii() encoding:NSASCIIStringEncoding]]; 360 } @catch(NSException* localException) { 361 // Do nothing. Class did not override valueForUndefinedKey:. 362 } 363 364 moveGlobalExceptionToExecState(exec); 365 } 366 367 return true; 368} 369 370JSValue ObjcInstance::getValueOfUndefinedField(ExecState* exec, const Identifier& property) const 371{ 372 JSValue result = jsUndefined(); 373 374 id targetObject = getObject(); 375 376 JSLock::DropAllLocks dropAllLocks(SilenceAssertionsOnly); // Can't put this inside the @try scope because it unwinds incorrectly. 377 378 // This check is not really necessary because NSObject implements 379 // valueForUndefinedKey:, and unfortunately the default implementation 380 // throws an exception. 381 if ([targetObject respondsToSelector:@selector(valueForUndefinedKey:)]){ 382 setGlobalException(nil); 383 384 @try { 385 id objcValue = [targetObject valueForUndefinedKey:[NSString stringWithCString:property.ascii() encoding:NSASCIIStringEncoding]]; 386 result = convertObjcValueToValue(exec, &objcValue, ObjcObjectType, m_rootObject.get()); 387 } @catch(NSException* localException) { 388 // Do nothing. Class did not override valueForUndefinedKey:. 389 } 390 391 moveGlobalExceptionToExecState(exec); 392 } 393 394 // Work around problem in some versions of GCC where result gets marked volatile and 395 // it can't handle copying from a volatile to non-volatile. 396 return const_cast<JSValue&>(result); 397} 398 399JSValue ObjcInstance::defaultValue(ExecState* exec, PreferredPrimitiveType hint) const 400{ 401 if (hint == PreferString) 402 return stringValue(exec); 403 if (hint == PreferNumber) 404 return numberValue(exec); 405 if ([_instance.get() isKindOfClass:[NSString class]]) 406 return stringValue(exec); 407 if ([_instance.get() isKindOfClass:[NSNumber class]]) 408 return numberValue(exec); 409 return valueOf(exec); 410} 411 412JSValue ObjcInstance::stringValue(ExecState* exec) const 413{ 414 return convertNSStringToString(exec, [getObject() description]); 415} 416 417JSValue ObjcInstance::numberValue(ExecState* exec) const 418{ 419 // FIXME: Implement something sensible 420 return jsNumber(exec, 0); 421} 422 423JSValue ObjcInstance::booleanValue() const 424{ 425 // FIXME: Implement something sensible 426 return jsBoolean(false); 427} 428 429JSValue ObjcInstance::valueOf(ExecState* exec) const 430{ 431 return stringValue(exec); 432} 433