]> git.saurik.com Git - apple/javascriptcore.git/blobdiff - API/ObjCCallbackFunction.mm
JavaScriptCore-7600.1.4.16.1.tar.gz
[apple/javascriptcore.git] / API / ObjCCallbackFunction.mm
index cc342f59e3e31f0b73d011681a7d7d7b2d93b02c..c62b7319dafa6ce050cf43cc7ebc58cf8675bc7f 100644 (file)
@@ -28,8 +28,9 @@
 
 #if JSC_OBJC_API_ENABLED
 
+#import "APICallbackFunction.h"
 #import "APICast.h"
-#import "APIShims.h"
+#import "DelayedReleaseScope.h"
 #import "Error.h"
 #import "JSCJSValueInlines.h"
 #import "JSCell.h"
@@ -127,7 +128,7 @@ private:
             return;
         }
 
-        *exception = toRef(JSC::createTypeError(toJS(contextRef), "Argument does not match Objective-C Class"));
+        *exception = toRef(JSC::createTypeError(toJS(contextRef), ASCIILiteral("Argument does not match Objective-C Class")));
     }
 
     Class m_class;
@@ -384,6 +385,7 @@ public:
 };
 
 enum CallbackType {
+    CallbackInitMethod,
     CallbackInstanceMethod,
     CallbackClassMethod,
     CallbackBlock
@@ -393,44 +395,52 @@ namespace JSC {
 
 class ObjCCallbackFunctionImpl {
 public:
-    ObjCCallbackFunctionImpl(JSContext *context, NSInvocation *invocation, CallbackType type, Class instanceClass, PassOwnPtr<CallbackArgument> arguments, PassOwnPtr<CallbackResult> result)
-        : m_context(context)
-        , m_type(type)
+    ObjCCallbackFunctionImpl(NSInvocation *invocation, CallbackType type, Class instanceClass, PassOwnPtr<CallbackArgument> arguments, PassOwnPtr<CallbackResult> result)
+        : m_type(type)
         , m_instanceClass([instanceClass retain])
         , m_invocation(invocation)
         , m_arguments(arguments)
         , m_result(result)
     {
-        ASSERT(type != CallbackInstanceMethod || instanceClass);
+        ASSERT((type != CallbackInstanceMethod && type != CallbackInitMethod) || instanceClass);
     }
 
-    ~ObjCCallbackFunctionImpl()
+    void destroy(Heap& heap)
     {
-        if (m_type != CallbackInstanceMethod)
-            [[m_invocation.get() target] release];
+        // We need to explicitly release the target since we didn't call 
+        // -retainArguments on m_invocation (and we don't want to do so).
+        if (m_type == CallbackBlock || m_type == CallbackClassMethod)
+            heap.releaseSoon(adoptNS([m_invocation.get() target]));
         [m_instanceClass release];
     }
 
     JSValueRef call(JSContext *context, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);
 
-    JSContext *context()
+    id wrappedBlock()
     {
-        return m_context.get();
+        return m_type == CallbackBlock ? [m_invocation target] : nil;
     }
 
-    void setContext(JSContext *context)
+    id wrappedConstructor()
     {
-        ASSERT(!m_context.get());
-        m_context.set(context);
+        switch (m_type) {
+        case CallbackBlock:
+            return [m_invocation target];
+        case CallbackInitMethod:
+            return m_instanceClass;
+        default:
+            return nil;
+        }
     }
 
-    id wrappedBlock()
+    bool isConstructible()
     {
-        return m_type == CallbackBlock ? [m_invocation target] : nil;
+        return !!wrappedBlock() || m_type == CallbackInitMethod;
     }
 
+    String name();
+
 private:
-    WeakContextRef m_context;
     CallbackType m_type;
     Class m_instanceClass;
     RetainPtr<NSInvocation> m_invocation;
@@ -444,20 +454,16 @@ static JSValueRef objCCallbackFunctionCallAsFunction(JSContextRef callerContext,
     // (1) We don't want to support the C-API's confusing drops-locks-once policy - should only drop locks if we can do so recursively.
     // (2) We're calling some JSC internals that require us to be on the 'inside' - e.g. createTypeError.
     // (3) We need to be locked (per context would be fine) against conflicting usage of the ObjCCallbackFunction's NSInvocation.
-    JSC::APIEntryShim entryShim(toJS(callerContext));
+    JSC::JSLockHolder locker(toJS(callerContext));
 
     ObjCCallbackFunction* callback = static_cast<ObjCCallbackFunction*>(toJS(function));
     ObjCCallbackFunctionImpl* impl = callback->impl();
-    JSContext *context = impl->context();
-    if (!context) {
-        context = [JSContext contextWithJSGlobalContextRef:toGlobalRef(toJS(callerContext)->lexicalGlobalObject()->globalExec())];
-        impl->setContext(context);
-    }
+    JSContext *context = [JSContext contextWithJSGlobalContextRef:toGlobalRef(callback->globalObject()->globalExec())];
 
     CallbackData callbackData;
     JSValueRef result;
     @autoreleasepool {
-        [context beginCallbackWithData:&callbackData thisValue:thisObject argumentCount:argumentCount arguments:arguments];
+        [context beginCallbackWithData:&callbackData calleeValue:function thisValue:thisObject argumentCount:argumentCount arguments:arguments];
         result = impl->call(context, thisObject, argumentCount, arguments, exception);
         if (context.exception)
             *exception = valueInternalValue(context.exception);
@@ -466,41 +472,112 @@ static JSValueRef objCCallbackFunctionCallAsFunction(JSContextRef callerContext,
     return result;
 }
 
+static JSObjectRef objCCallbackFunctionCallAsConstructor(JSContextRef callerContext, JSObjectRef constructor, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
+{
+    JSC::JSLockHolder locker(toJS(callerContext));
+
+    ObjCCallbackFunction* callback = static_cast<ObjCCallbackFunction*>(toJS(constructor));
+    ObjCCallbackFunctionImpl* impl = callback->impl();
+    JSContext *context = [JSContext contextWithJSGlobalContextRef:toGlobalRef(toJS(callerContext)->lexicalGlobalObject()->globalExec())];
+
+    CallbackData callbackData;
+    JSValueRef result;
+    @autoreleasepool {
+        [context beginCallbackWithData:&callbackData calleeValue:constructor thisValue:nil argumentCount:argumentCount arguments:arguments];
+        result = impl->call(context, NULL, argumentCount, arguments, exception);
+        if (context.exception)
+            *exception = valueInternalValue(context.exception);
+        [context endCallbackWithData:&callbackData];
+    }
+
+    JSGlobalContextRef contextRef = [context JSGlobalContextRef];
+    if (*exception)
+        return 0;
+
+    if (!JSValueIsObject(contextRef, result)) {
+        *exception = toRef(JSC::createTypeError(toJS(contextRef), ASCIILiteral("Objective-C blocks called as constructors must return an object.")));
+        return 0;
+    }
+    return (JSObjectRef)result;
+}
+
 const JSC::ClassInfo ObjCCallbackFunction::s_info = { "CallbackFunction", &Base::s_info, 0, 0, CREATE_METHOD_TABLE(ObjCCallbackFunction) };
 
-ObjCCallbackFunction::ObjCCallbackFunction(JSC::JSGlobalObject* globalObject, JSObjectCallAsFunctionCallback callback, PassOwnPtr<ObjCCallbackFunctionImpl> impl)
-    : Base(globalObject, globalObject->objcCallbackFunctionStructure(), callback)
+ObjCCallbackFunction::ObjCCallbackFunction(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSObjectCallAsFunctionCallback functionCallback, JSObjectCallAsConstructorCallback constructCallback, PassOwnPtr<ObjCCallbackFunctionImpl> impl)
+    : Base(vm, globalObject->objcCallbackFunctionStructure())
+    , m_functionCallback(functionCallback)
+    , m_constructCallback(constructCallback)
     , m_impl(impl)
 {
 }
 
-ObjCCallbackFunction* ObjCCallbackFunction::create(JSC::ExecState* exec, JSC::JSGlobalObject* globalObject, const String& name, PassOwnPtr<ObjCCallbackFunctionImpl> impl)
+ObjCCallbackFunction* ObjCCallbackFunction::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, const String& name, PassOwnPtr<ObjCCallbackFunctionImpl> impl)
 {
-    ObjCCallbackFunction* function = new (NotNull, allocateCell<ObjCCallbackFunction>(*exec->heap())) ObjCCallbackFunction(globalObject, objCCallbackFunctionCallAsFunction, impl);
-    function->finishCreation(exec->vm(), name);
+    ObjCCallbackFunction* function = new (NotNull, allocateCell<ObjCCallbackFunction>(vm.heap)) ObjCCallbackFunction(vm, globalObject, objCCallbackFunctionCallAsFunction, objCCallbackFunctionCallAsConstructor, impl);
+    function->finishCreation(vm, name);
     return function;
 }
 
 void ObjCCallbackFunction::destroy(JSCell* cell)
 {
-    static_cast<ObjCCallbackFunction*>(cell)->ObjCCallbackFunction::~ObjCCallbackFunction();
+    ObjCCallbackFunction& function = *jsCast<ObjCCallbackFunction*>(cell);
+    function.impl()->destroy(*Heap::heap(cell));
+    function.~ObjCCallbackFunction();
+}
+
+
+CallType ObjCCallbackFunction::getCallData(JSCell*, CallData& callData)
+{
+    callData.native.function = APICallbackFunction::call<ObjCCallbackFunction>;
+    return CallTypeHost;
+}
+
+ConstructType ObjCCallbackFunction::getConstructData(JSCell* cell, ConstructData& constructData)
+{
+    ObjCCallbackFunction* callback = jsCast<ObjCCallbackFunction*>(cell);
+    if (!callback->impl()->isConstructible())
+        return Base::getConstructData(cell, constructData);
+    constructData.native.function = APICallbackFunction::construct<ObjCCallbackFunction>;
+    return ConstructTypeHost;
+}
+
+String ObjCCallbackFunctionImpl::name()
+{
+    if (m_type == CallbackInitMethod)
+        return class_getName(m_instanceClass);
+    // FIXME: Maybe we could support having the selector as the name of the non-init 
+    // functions to make it a bit more user-friendly from the JS side?
+    return "";
 }
 
 JSValueRef ObjCCallbackFunctionImpl::call(JSContext *context, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
 {
     JSGlobalContextRef contextRef = [context JSGlobalContextRef];
 
+    id target;
     size_t firstArgument;
     switch (m_type) {
+    case CallbackInitMethod: {
+        RELEASE_ASSERT(!thisObject);
+        target = [m_instanceClass alloc];
+        if (!target || ![target isKindOfClass:m_instanceClass]) {
+            *exception = toRef(JSC::createTypeError(toJS(contextRef), ASCIILiteral("self type check failed for Objective-C instance method")));
+            return JSValueMakeUndefined(contextRef);
+        }
+        [m_invocation setTarget:target];
+        firstArgument = 2;
+        break;
+    }
     case CallbackInstanceMethod: {
-        id target = tryUnwrapObjcObject(contextRef, thisObject);
+        target = tryUnwrapObjcObject(contextRef, thisObject);
         if (!target || ![target isKindOfClass:m_instanceClass]) {
-            *exception = toRef(JSC::createTypeError(toJS(contextRef), "self type check failed for Objective-C instance method"));
+            *exception = toRef(JSC::createTypeError(toJS(contextRef), ASCIILiteral("self type check failed for Objective-C instance method")));
             return JSValueMakeUndefined(contextRef);
         }
         [m_invocation setTarget:target];
+        firstArgument = 2;
+        break;
     }
-    // fallthrough - firstArgument for CallbackInstanceMethod is also 2!
     case CallbackClassMethod:
         firstArgument = 2;
         break;
@@ -519,7 +596,18 @@ JSValueRef ObjCCallbackFunctionImpl::call(JSContext *context, JSObjectRef thisOb
 
     [m_invocation invoke];
 
-    return m_result->get(m_invocation.get(), context, exception);
+    JSValueRef result = m_result->get(m_invocation.get(), context, exception);
+
+    // Balance our call to -alloc with a call to -autorelease. We have to do this after calling -init
+    // because init family methods are allowed to release the allocated object and return something 
+    // else in its place.
+    if (m_type == CallbackInitMethod) {
+        id objcResult = tryUnwrapObjcObject(contextRef, result);
+        if (objcResult)
+            [objcResult autorelease];
+    }
+
+    return result;
 }
 
 } // namespace JSC
@@ -543,6 +631,9 @@ inline bool skipNumber(const char*& position)
 
 static JSObjectRef objCCallbackFunctionForInvocation(JSContext *context, NSInvocation *invocation, CallbackType type, Class instanceClass, const char* signatureWithObjcClasses)
 {
+    if (!signatureWithObjcClasses)
+        return nil;
+
     const char* position = signatureWithObjcClasses;
 
     OwnPtr<CallbackResult> result = adoptPtr(parseObjCType<ResultTypeDelegate>(position));
@@ -550,6 +641,7 @@ static JSObjectRef objCCallbackFunctionForInvocation(JSContext *context, NSInvoc
         return nil;
 
     switch (type) {
+    case CallbackInitMethod:
     case CallbackInstanceMethod:
     case CallbackClassMethod:
         // Methods are passed two implicit arguments - (id)self, and the selector.
@@ -580,18 +672,26 @@ static JSObjectRef objCCallbackFunctionForInvocation(JSContext *context, NSInvoc
     }
 
     JSC::ExecState* exec = toJS([context JSGlobalContextRef]);
-    JSC::APIEntryShim shim(exec);
-    OwnPtr<JSC::ObjCCallbackFunctionImpl> impl = adoptPtr(new JSC::ObjCCallbackFunctionImpl(context, invocation, type, instanceClass, arguments.release(), result.release()));
-    // FIXME: Maybe we could support having the selector as the name of the function to make it a bit more user-friendly from the JS side?
-    return toRef(JSC::ObjCCallbackFunction::create(exec, exec->lexicalGlobalObject(), "", impl.release()));
+    JSC::JSLockHolder locker(exec);
+    OwnPtr<JSC::ObjCCallbackFunctionImpl> impl = adoptPtr(new JSC::ObjCCallbackFunctionImpl(invocation, type, instanceClass, arguments.release(), result.release()));
+    return toRef(JSC::ObjCCallbackFunction::create(exec->vm(), exec->lexicalGlobalObject(), impl->name(), impl.release()));
+}
+
+JSObjectRef objCCallbackFunctionForInit(JSContext *context, Class cls, Protocol *protocol, SEL sel, const char* types)
+{
+    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:types]];
+    [invocation setSelector:sel];
+    return objCCallbackFunctionForInvocation(context, invocation, CallbackInitMethod, cls, _protocol_getMethodTypeEncoding(protocol, sel, YES, YES));
 }
 
 JSObjectRef objCCallbackFunctionForMethod(JSContext *context, Class cls, Protocol *protocol, BOOL isInstanceMethod, SEL sel, const char* types)
 {
     NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:types]];
     [invocation setSelector:sel];
+    // We need to retain the target Class because m_invocation doesn't retain it
+    // by default (and we don't want it to).
     if (!isInstanceMethod)
-        [invocation setTarget:cls];
+        [invocation setTarget:[cls retain]];
     return objCCallbackFunctionForInvocation(context, invocation, isInstanceMethod ? CallbackInstanceMethod : CallbackClassMethod, isInstanceMethod ? cls : nil, _protocol_getMethodTypeEncoding(protocol, sel, YES, isInstanceMethod));
 }
 
@@ -601,15 +701,24 @@ JSObjectRef objCCallbackFunctionForBlock(JSContext *context, id target)
         return 0;
     const char* signature = _Block_signature(target);
     NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:signature]];
+
+    // We don't want to use -retainArguments because that leaks memory. Arguments 
+    // would be retained indefinitely between invocations of the callback.
+    // Additionally, we copy the target because we want the block to stick around
+    // until the ObjCCallbackFunctionImpl is destroyed.
     [invocation setTarget:[target copy]];
+
     return objCCallbackFunctionForInvocation(context, invocation, CallbackBlock, nil, signature);
 }
 
-id tryUnwrapBlock(JSObjectRef object)
+id tryUnwrapConstructor(JSObjectRef object)
 {
-    if (!toJS(object)->inherits(&JSC::ObjCCallbackFunction::s_info))
+    if (!toJS(object)->inherits(JSC::ObjCCallbackFunction::info()))
+        return nil;
+    JSC::ObjCCallbackFunctionImpl* impl = static_cast<JSC::ObjCCallbackFunction*>(toJS(object))->impl();
+    if (!impl->isConstructible())
         return nil;
-    return static_cast<JSC::ObjCCallbackFunction*>(toJS(object))->impl()->wrappedBlock();
+    return impl->wrappedConstructor();
 }
 
 #endif