X-Git-Url: https://git.saurik.com/apple/javascriptcore.git/blobdiff_plain/93a3786624b2768d89bfa27e46598dc64e2fb70a..ed1e77d3adeb83d26fd1dfb16dd84cabdcefd250:/API/ObjCCallbackFunction.mm diff --git a/API/ObjCCallbackFunction.mm b/API/ObjCCallbackFunction.mm index cc342f5..bba9294 100644 --- a/API/ObjCCallbackFunction.mm +++ b/API/ObjCCallbackFunction.mm @@ -28,8 +28,8 @@ #if JSC_OBJC_API_ENABLED +#import "APICallbackFunction.h" #import "APICast.h" -#import "APIShims.h" #import "Error.h" #import "JSCJSValueInlines.h" #import "JSCell.h" @@ -47,7 +47,7 @@ public: virtual ~CallbackArgument(); virtual void set(NSInvocation *, NSInteger, JSContext *, JSValueRef, JSValueRef*) = 0; - OwnPtr m_next; + std::unique_ptr m_next; }; CallbackArgument::~CallbackArgument() @@ -99,24 +99,17 @@ class CallbackArgumentId : public CallbackArgument { class CallbackArgumentOfClass : public CallbackArgument { public: CallbackArgumentOfClass(Class cls) - : CallbackArgument() - , m_class(cls) + : m_class(cls) { - [m_class retain]; } private: - virtual ~CallbackArgumentOfClass() - { - [m_class release]; - } - virtual void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) override { JSGlobalContextRef contextRef = [context JSGlobalContextRef]; id object = tryUnwrapObjcObject(contextRef, argument); - if (object && [object isKindOfClass:m_class]) { + if (object && [object isKindOfClass:m_class.get()]) { [invocation setArgument:&object atIndex:argumentNumber]; return; } @@ -127,10 +120,10 @@ 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; + RetainPtr m_class; }; class CallbackArgumentNSNumber : public CallbackArgument { @@ -196,34 +189,34 @@ private: class ArgumentTypeDelegate { public: - typedef CallbackArgument* ResultType; + typedef std::unique_ptr ResultType; template static ResultType typeInteger() { - return new CallbackArgumentInteger; + return std::make_unique>(); } template static ResultType typeDouble() { - return new CallbackArgumentDouble; + return std::make_unique>(); } static ResultType typeBool() { - return new CallbackArgumentBoolean; + return std::make_unique(); } static ResultType typeVoid() { RELEASE_ASSERT_NOT_REACHED(); - return 0; + return nullptr; } static ResultType typeId() { - return new CallbackArgumentId; + return std::make_unique(); } static ResultType typeOfClass(const char* begin, const char* end) @@ -231,35 +224,35 @@ public: StringRange copy(begin, end); Class cls = objc_getClass(copy); if (!cls) - return 0; + return nullptr; if (cls == [JSValue class]) - return new CallbackArgumentJSValue; + return std::make_unique(); if (cls == [NSString class]) - return new CallbackArgumentNSString; + return std::make_unique(); if (cls == [NSNumber class]) - return new CallbackArgumentNSNumber; + return std::make_unique(); if (cls == [NSDate class]) - return new CallbackArgumentNSDate; + return std::make_unique(); if (cls == [NSArray class]) - return new CallbackArgumentNSArray; + return std::make_unique(); if (cls == [NSDictionary class]) - return new CallbackArgumentNSDictionary; + return std::make_unique(); - return new CallbackArgumentOfClass(cls); + return std::make_unique(cls); } static ResultType typeBlock(const char*, const char*) { - return nil; + return nullptr; } static ResultType typeStruct(const char* begin, const char* end) { StringRange copy(begin, end); if (NSInvocation *invocation = valueToTypeInvocationFor(copy)) - return new CallbackArgumentStruct(invocation, copy); - return 0; + return std::make_unique(invocation, copy); + return nullptr; } }; @@ -335,55 +328,56 @@ private: class ResultTypeDelegate { public: - typedef CallbackResult* ResultType; + typedef std::unique_ptr ResultType; template static ResultType typeInteger() { - return new CallbackResultNumeric; + return std::make_unique>(); } template static ResultType typeDouble() { - return new CallbackResultNumeric; + return std::make_unique>(); } static ResultType typeBool() { - return new CallbackResultBoolean; + return std::make_unique(); } static ResultType typeVoid() { - return new CallbackResultVoid; + return std::make_unique(); } static ResultType typeId() { - return new CallbackResultId(); + return std::make_unique(); } static ResultType typeOfClass(const char*, const char*) { - return new CallbackResultId(); + return std::make_unique(); } static ResultType typeBlock(const char*, const char*) { - return new CallbackResultId(); + return std::make_unique(); } static ResultType typeStruct(const char* begin, const char* end) { StringRange copy(begin, end); if (NSInvocation *invocation = typeToValueInvocationFor(copy)) - return new CallbackResultStruct(invocation, copy); - return 0; + return std::make_unique(invocation, copy); + return nullptr; } }; enum CallbackType { + CallbackInitMethod, CallbackInstanceMethod, CallbackClassMethod, CallbackBlock @@ -393,49 +387,57 @@ namespace JSC { class ObjCCallbackFunctionImpl { public: - ObjCCallbackFunctionImpl(JSContext *context, NSInvocation *invocation, CallbackType type, Class instanceClass, PassOwnPtr arguments, PassOwnPtr result) - : m_context(context) - , m_type(type) - , m_instanceClass([instanceClass retain]) + ObjCCallbackFunctionImpl(NSInvocation *invocation, CallbackType type, Class instanceClass, std::unique_ptr arguments, std::unique_ptr result) + : m_type(type) + , m_instanceClass(instanceClass) , m_invocation(invocation) - , m_arguments(arguments) - , m_result(result) + , m_arguments(WTF::move(arguments)) + , m_result(WTF::move(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]; - [m_instanceClass 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 = nil; } 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.get(); + 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_instanceClass; RetainPtr m_invocation; - OwnPtr m_arguments; - OwnPtr m_result; + std::unique_ptr m_arguments; + std::unique_ptr m_result; }; static JSValueRef objCCallbackFunctionCallAsFunction(JSContextRef callerContext, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) @@ -444,20 +446,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 +464,112 @@ static JSValueRef objCCallbackFunctionCallAsFunction(JSContextRef callerContext, return result; } -const JSC::ClassInfo ObjCCallbackFunction::s_info = { "CallbackFunction", &Base::s_info, 0, 0, CREATE_METHOD_TABLE(ObjCCallbackFunction) }; +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())]; -ObjCCallbackFunction::ObjCCallbackFunction(JSC::JSGlobalObject* globalObject, JSObjectCallAsFunctionCallback callback, PassOwnPtr impl) - : Base(globalObject, globalObject->objcCallbackFunctionStructure(), callback) - , m_impl(impl) + CallbackData callbackData; + JSValueRef result; + @autoreleasepool { + [context beginCallbackWithData:&callbackData calleeValue:constructor thisValue:nullptr argumentCount:argumentCount arguments:arguments]; + result = impl->call(context, nullptr, argumentCount, arguments, exception); + if (context.exception) + *exception = valueInternalValue(context.exception); + [context endCallbackWithData:&callbackData]; + } + + JSGlobalContextRef contextRef = [context JSGlobalContextRef]; + if (*exception) + return nullptr; + + if (!JSValueIsObject(contextRef, result)) { + *exception = toRef(JSC::createTypeError(toJS(contextRef), ASCIILiteral("Objective-C blocks called as constructors must return an object."))); + return nullptr; + } + return (JSObjectRef)result; +} + +const JSC::ClassInfo ObjCCallbackFunction::s_info = { "CallbackFunction", &Base::s_info, 0, CREATE_METHOD_TABLE(ObjCCallbackFunction) }; + +ObjCCallbackFunction::ObjCCallbackFunction(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSObjectCallAsFunctionCallback functionCallback, JSObjectCallAsConstructorCallback constructCallback, std::unique_ptr impl) + : Base(vm, globalObject->objcCallbackFunctionStructure()) + , m_functionCallback(functionCallback) + , m_constructCallback(constructCallback) + , m_impl(WTF::move(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, std::unique_ptr 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, WTF::move(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.get()); + // 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.get()]) { + *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); - if (!target || ![target isKindOfClass:m_instanceClass]) { - *exception = toRef(JSC::createTypeError(toJS(contextRef), "self type check failed for Objective-C instance method")); + target = tryUnwrapObjcObject(contextRef, thisObject); + if (!target || ![target isKindOfClass:m_instanceClass.get()]) { + *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 +588,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 @@ -533,7 +613,7 @@ static bool blockSignatureContainsClass() return containsClass; } -inline bool skipNumber(const char*& position) +static inline bool skipNumber(const char*& position) { if (!isASCIIDigit(*position)) return false; @@ -543,73 +623,95 @@ inline bool skipNumber(const char*& position) static JSObjectRef objCCallbackFunctionForInvocation(JSContext *context, NSInvocation *invocation, CallbackType type, Class instanceClass, const char* signatureWithObjcClasses) { + if (!signatureWithObjcClasses) + return nullptr; + const char* position = signatureWithObjcClasses; - OwnPtr result = adoptPtr(parseObjCType(position)); + auto result = parseObjCType(position); if (!result || !skipNumber(position)) - return nil; + return nullptr; switch (type) { + case CallbackInitMethod: case CallbackInstanceMethod: case CallbackClassMethod: // Methods are passed two implicit arguments - (id)self, and the selector. if ('@' != *position++ || !skipNumber(position) || ':' != *position++ || !skipNumber(position)) - return nil; + return nullptr; break; case CallbackBlock: // Blocks are passed one implicit argument - the block, of type "@?". if (('@' != *position++) || ('?' != *position++) || !skipNumber(position)) - return nil; + return nullptr; // Only allow arguments of type 'id' if the block signature contains the NS type information. if ((!blockSignatureContainsClass() && strchr(position, '@'))) - return nil; + return nullptr; break; } - OwnPtr arguments = 0; - OwnPtr* nextArgument = &arguments; + std::unique_ptr arguments; + auto* nextArgument = &arguments; unsigned argumentCount = 0; while (*position) { - OwnPtr argument = adoptPtr(parseObjCType(position)); + auto argument = parseObjCType(position); if (!argument || !skipNumber(position)) - return nil; + return nullptr; - *nextArgument = argument.release(); + *nextArgument = WTF::move(argument); nextArgument = &(*nextArgument)->m_next; ++argumentCount; } 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); + auto impl = std::make_unique(invocation, type, instanceClass, WTF::move(arguments), WTF::move(result)); + const String& name = impl->name(); + return toRef(JSC::ObjCCallbackFunction::create(exec->vm(), exec->lexicalGlobalObject(), name, WTF::move(impl))); +} + +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). + // FIXME: What releases it? 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)); } JSObjectRef objCCallbackFunctionForBlock(JSContext *context, id target) { if (!_Block_has_signature(target)) - return 0; + return nullptr; 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