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