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