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