Home | History | Annotate | Download | only in objc
      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 
     48 using namespace JSC::Bindings;
     49 using namespace JSC;
     50 
     51 static NSString *s_exception;
     52 static JSGlobalObject* s_exceptionEnvironment; // No need to protect this value, since we just use it for a pointer comparison.
     53 static NSMapTable *s_instanceWrapperCache;
     54 
     55 static 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 
     67 RuntimeObject* ObjcInstance::newRuntimeObject(ExecState* exec)
     68 {
     69     return new (exec) ObjCRuntimeObject(exec, exec->lexicalGlobalObject(), this);
     70 }
     71 
     72 void 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 
     81 void 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 
     98 ObjcInstance::ObjcInstance(id instance, PassRefPtr<RootObject> rootObject)
     99     : Instance(rootObject)
    100     , _instance(instance)
    101     , _class(0)
    102     , _pool(0)
    103     , _beginCount(0)
    104 {
    105 }
    106 
    107 PassRefPtr<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 
    118 ObjcInstance::~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 
    134 static 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 
    146 void ObjcInstance::virtualBegin()
    147 {
    148     if (!_pool)
    149         _pool = allocateAutoReleasePool();
    150     _beginCount++;
    151 }
    152 
    153 void ObjcInstance::virtualEnd()
    154 {
    155     _beginCount--;
    156     ASSERT(_beginCount >= 0);
    157     if (!_beginCount) {
    158         [_pool drain];
    159         _pool = 0;
    160     }
    161 }
    162 
    163 Bindings::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 
    172 bool ObjcInstance::supportsInvokeDefaultMethod() const
    173 {
    174     return [_instance.get() respondsToSelector:@selector(invokeDefaultMethodWithArguments:)];
    175 }
    176 
    177 class ObjCRuntimeMethod : public RuntimeMethod {
    178 public:
    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 
    195 const ClassInfo ObjCRuntimeMethod::s_info = { "ObjCRuntimeMethod", &RuntimeMethod::s_info, 0, 0 };
    196 
    197 JSValue 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 
    203 JSValue 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 
    217 JSValue 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 
    332 JSValue 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 
    383 bool 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 
    411 JSValue 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 
    440 JSValue 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 
    453 JSValue ObjcInstance::stringValue(ExecState* exec) const
    454 {
    455     return convertNSStringToString(exec, [getObject() description]);
    456 }
    457 
    458 JSValue ObjcInstance::numberValue(ExecState*) const
    459 {
    460     // FIXME:  Implement something sensible
    461     return jsNumber(0);
    462 }
    463 
    464 JSValue ObjcInstance::booleanValue() const
    465 {
    466     // FIXME:  Implement something sensible
    467     return jsBoolean(false);
    468 }
    469 
    470 JSValue ObjcInstance::valueOf(ExecState* exec) const
    471 {
    472     return stringValue(exec);
    473 }
    474