X-Git-Url: https://git.saurik.com/apple/javascriptcore.git/blobdiff_plain/93a3786624b2768d89bfa27e46598dc64e2fb70a..2656c66b5b30d5597e842a751c7f19ad6c2fe31a:/API/ObjCCallbackFunction.mm?ds=sidebyside diff --git a/API/ObjCCallbackFunction.mm b/API/ObjCCallbackFunction.mm index cc342f5..c62b731 100644 --- a/API/ObjCCallbackFunction.mm +++ b/API/ObjCCallbackFunction.mm @@ -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 arguments, PassOwnPtr result) - : m_context(context) - , m_type(type) + ObjCCallbackFunctionImpl(NSInvocation *invocation, CallbackType type, Class instanceClass, PassOwnPtr arguments, PassOwnPtr 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 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(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(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 impl) - : Base(globalObject, globalObject->objcCallbackFunctionStructure(), callback) +ObjCCallbackFunction::ObjCCallbackFunction(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSObjectCallAsFunctionCallback functionCallback, JSObjectCallAsConstructorCallback constructCallback, PassOwnPtr 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 impl) +ObjCCallbackFunction* ObjCCallbackFunction::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, const String& name, PassOwnPtr impl) { - ObjCCallbackFunction* function = new (NotNull, allocateCell(*exec->heap())) ObjCCallbackFunction(globalObject, objCCallbackFunctionCallAsFunction, impl); - function->finishCreation(exec->vm(), name); + ObjCCallbackFunction* function = new (NotNull, allocateCell(vm.heap)) ObjCCallbackFunction(vm, globalObject, objCCallbackFunctionCallAsFunction, objCCallbackFunctionCallAsConstructor, impl); + function->finishCreation(vm, name); return function; } void ObjCCallbackFunction::destroy(JSCell* cell) { - static_cast(cell)->ObjCCallbackFunction::~ObjCCallbackFunction(); + ObjCCallbackFunction& function = *jsCast(cell); + function.impl()->destroy(*Heap::heap(cell)); + function.~ObjCCallbackFunction(); +} + + +CallType ObjCCallbackFunction::getCallData(JSCell*, CallData& callData) +{ + callData.native.function = APICallbackFunction::call; + return CallTypeHost; +} + +ConstructType ObjCCallbackFunction::getConstructData(JSCell* cell, ConstructData& constructData) +{ + ObjCCallbackFunction* callback = jsCast(cell); + if (!callback->impl()->isConstructible()) + return Base::getConstructData(cell, constructData); + constructData.native.function = APICallbackFunction::construct; + 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 result = adoptPtr(parseObjCType(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 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 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(toJS(object))->impl(); + if (!impl->isConstructible()) return nil; - return static_cast(toJS(object))->impl()->wrappedBlock(); + return impl->wrappedConstructor(); } #endif