ObjCCallbackFunction.mm
1 /* 2 * Copyright (C) 2013-2020 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 "JavaScriptCore.h" 28 29 #if JSC_OBJC_API_ENABLED 30 31 #import "APICallbackFunction.h" 32 #import "APICast.h" 33 #import "Error.h" 34 #import "JSCell.h" 35 #import "JSCInlines.h" 36 #import "JSContextInternal.h" 37 #import "JSWrapperMap.h" 38 #import "JSValueInternal.h" 39 #import "ObjCCallbackFunction.h" 40 #import "ObjcRuntimeExtras.h" 41 #import "StructureInlines.h" 42 #import <objc/runtime.h> 43 #import <wtf/RetainPtr.h> 44 45 class CallbackArgument { 46 WTF_MAKE_FAST_ALLOCATED; 47 public: 48 virtual ~CallbackArgument(); 49 virtual void set(NSInvocation *, NSInteger, JSContext *, JSValueRef, JSValueRef*) = 0; 50 51 std::unique_ptr<CallbackArgument> m_next; 52 }; 53 54 CallbackArgument::~CallbackArgument() 55 { 56 } 57 58 class CallbackArgumentBoolean final : public CallbackArgument { 59 void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef*) final 60 { 61 bool value = JSValueToBoolean([context JSGlobalContextRef], argument); 62 [invocation setArgument:&value atIndex:argumentNumber]; 63 } 64 }; 65 66 template<typename T> 67 class CallbackArgumentInteger final : public CallbackArgument { 68 void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) final 69 { 70 ASSERT(exception && !*exception); 71 T value = (T)JSC::toInt32(JSValueToNumber([context JSGlobalContextRef], argument, exception)); 72 if (*exception) 73 return; 74 [invocation setArgument:&value atIndex:argumentNumber]; 75 } 76 }; 77 78 template<typename T> 79 class CallbackArgumentDouble final : public CallbackArgument { 80 void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) final 81 { 82 ASSERT(exception && !*exception); 83 T value = (T)JSValueToNumber([context JSGlobalContextRef], argument, exception); 84 if (*exception) 85 return; 86 [invocation setArgument:&value atIndex:argumentNumber]; 87 } 88 }; 89 90 class CallbackArgumentJSValue final : public CallbackArgument { 91 void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef*) final 92 { 93 JSValue *value = [JSValue valueWithJSValueRef:argument inContext:context]; 94 [invocation setArgument:&value atIndex:argumentNumber]; 95 } 96 }; 97 98 class CallbackArgumentId final : public CallbackArgument { 99 void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef*) final 100 { 101 id value = valueToObject(context, argument); 102 [invocation setArgument:&value atIndex:argumentNumber]; 103 } 104 }; 105 106 class CallbackArgumentOfClass final : public CallbackArgument { 107 public: 108 CallbackArgumentOfClass(Class cls) 109 : m_class(cls) 110 { 111 } 112 113 private: 114 void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) final 115 { 116 ASSERT(exception && !*exception); 117 JSGlobalContextRef contextRef = [context JSGlobalContextRef]; 118 119 id object = tryUnwrapObjcObject(contextRef, argument); 120 if (object && [object isKindOfClass:m_class.get()]) { 121 [invocation setArgument:&object atIndex:argumentNumber]; 122 return; 123 } 124 125 if (JSValueIsNull(contextRef, argument) || JSValueIsUndefined(contextRef, argument)) { 126 object = nil; 127 [invocation setArgument:&object atIndex:argumentNumber]; 128 return; 129 } 130 131 *exception = toRef(JSC::createTypeError(toJS(contextRef), "Argument does not match Objective-C Class"_s)); 132 } 133 134 RetainPtr<Class> m_class; 135 }; 136 137 class CallbackArgumentNSNumber final : public CallbackArgument { 138 void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) final 139 { 140 ASSERT(exception && !*exception); 141 id value = valueToNumber([context JSGlobalContextRef], argument, exception); 142 if (*exception) 143 return; 144 [invocation setArgument:&value atIndex:argumentNumber]; 145 } 146 }; 147 148 class CallbackArgumentNSString final : public CallbackArgument { 149 void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) final 150 { 151 ASSERT(exception && !*exception); 152 id value = valueToString([context JSGlobalContextRef], argument, exception); 153 if (*exception) 154 return; 155 [invocation setArgument:&value atIndex:argumentNumber]; 156 } 157 }; 158 159 class CallbackArgumentNSDate final : public CallbackArgument { 160 void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) final 161 { 162 ASSERT(exception && !*exception); 163 id value = valueToDate([context JSGlobalContextRef], argument, exception); 164 if (*exception) 165 return; 166 [invocation setArgument:&value atIndex:argumentNumber]; 167 } 168 }; 169 170 class CallbackArgumentNSArray final : public CallbackArgument { 171 void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) final 172 { 173 ASSERT(exception && !*exception); 174 id value = valueToArray([context JSGlobalContextRef], argument, exception); 175 if (*exception) 176 return; 177 [invocation setArgument:&value atIndex:argumentNumber]; 178 } 179 }; 180 181 class CallbackArgumentNSDictionary final : public CallbackArgument { 182 void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) final 183 { 184 ASSERT(exception && !*exception); 185 id value = valueToDictionary([context JSGlobalContextRef], argument, exception); 186 if (*exception) 187 return; 188 [invocation setArgument:&value atIndex:argumentNumber]; 189 } 190 }; 191 192 class CallbackArgumentStruct final : public CallbackArgument { 193 public: 194 CallbackArgumentStruct(NSInvocation *conversionInvocation, const char* encodedType) 195 : m_conversionInvocation(conversionInvocation) 196 , m_buffer(encodedType) 197 { 198 } 199 200 private: 201 void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef*) final 202 { 203 JSValue *value = [JSValue valueWithJSValueRef:argument inContext:context]; 204 [m_conversionInvocation invokeWithTarget:value]; 205 [m_conversionInvocation getReturnValue:m_buffer]; 206 [invocation setArgument:m_buffer atIndex:argumentNumber]; 207 } 208 209 RetainPtr<NSInvocation> m_conversionInvocation; 210 StructBuffer m_buffer; 211 }; 212 213 class ArgumentTypeDelegate final { 214 public: 215 typedef std::unique_ptr<CallbackArgument> ResultType; 216 217 template<typename T> 218 static ResultType typeInteger() 219 { 220 return makeUnique<CallbackArgumentInteger<T>>(); 221 } 222 223 template<typename T> 224 static ResultType typeDouble() 225 { 226 return makeUnique<CallbackArgumentDouble<T>>(); 227 } 228 229 static ResultType typeBool() 230 { 231 return makeUnique<CallbackArgumentBoolean>(); 232 } 233 234 static ResultType typeVoid() 235 { 236 RELEASE_ASSERT_NOT_REACHED(); 237 return nullptr; 238 } 239 240 static ResultType typeId() 241 { 242 return makeUnique<CallbackArgumentId>(); 243 } 244 245 static ResultType typeOfClass(const char* begin, const char* end) 246 { 247 StringRange copy(begin, end); 248 Class cls = objc_getClass(copy); 249 if (!cls) 250 return nullptr; 251 252 if (cls == [JSValue class]) 253 return makeUnique<CallbackArgumentJSValue>(); 254 if (cls == [NSString class]) 255 return makeUnique<CallbackArgumentNSString>(); 256 if (cls == [NSNumber class]) 257 return makeUnique<CallbackArgumentNSNumber>(); 258 if (cls == [NSDate class]) 259 return makeUnique<CallbackArgumentNSDate>(); 260 if (cls == [NSArray class]) 261 return makeUnique<CallbackArgumentNSArray>(); 262 if (cls == [NSDictionary class]) 263 return makeUnique<CallbackArgumentNSDictionary>(); 264 265 return makeUnique<CallbackArgumentOfClass>(cls); 266 } 267 268 static ResultType typeBlock(const char*, const char*) 269 { 270 return nullptr; 271 } 272 273 static ResultType typeStruct(const char* begin, const char* end) 274 { 275 StringRange copy(begin, end); 276 if (NSInvocation *invocation = valueToTypeInvocationFor(copy)) 277 return makeUnique<CallbackArgumentStruct>(invocation, copy); 278 return nullptr; 279 } 280 }; 281 282 class CallbackResult { 283 WTF_MAKE_FAST_ALLOCATED; 284 public: 285 virtual ~CallbackResult() 286 { 287 } 288 289 virtual JSValueRef get(NSInvocation *, JSContext *, JSValueRef*) = 0; 290 }; 291 292 class CallbackResultVoid final : public CallbackResult { 293 JSValueRef get(NSInvocation *, JSContext *context, JSValueRef*) final 294 { 295 return JSValueMakeUndefined([context JSGlobalContextRef]); 296 } 297 }; 298 299 class CallbackResultId final : public CallbackResult { 300 JSValueRef get(NSInvocation *invocation, JSContext *context, JSValueRef*) final 301 { 302 id value; 303 [invocation getReturnValue:&value]; 304 return objectToValue(context, value); 305 } 306 }; 307 308 template<typename T> 309 class CallbackResultNumeric final : public CallbackResult { 310 JSValueRef get(NSInvocation *invocation, JSContext *context, JSValueRef*) final 311 { 312 T value; 313 [invocation getReturnValue:&value]; 314 return JSValueMakeNumber([context JSGlobalContextRef], value); 315 } 316 }; 317 318 class CallbackResultBoolean final : public CallbackResult { 319 JSValueRef get(NSInvocation *invocation, JSContext *context, JSValueRef*) final 320 { 321 bool value; 322 [invocation getReturnValue:&value]; 323 return JSValueMakeBoolean([context JSGlobalContextRef], value); 324 } 325 }; 326 327 class CallbackResultStruct final : public CallbackResult { 328 public: 329 CallbackResultStruct(NSInvocation *conversionInvocation, const char* encodedType) 330 : m_conversionInvocation(conversionInvocation) 331 , m_buffer(encodedType) 332 { 333 } 334 335 private: 336 JSValueRef get(NSInvocation *invocation, JSContext *context, JSValueRef*) final 337 { 338 [invocation getReturnValue:m_buffer]; 339 340 [m_conversionInvocation setArgument:m_buffer atIndex:2]; 341 [m_conversionInvocation setArgument:&context atIndex:3]; 342 [m_conversionInvocation invokeWithTarget:[JSValue class]]; 343 344 JSValue *value; 345 [m_conversionInvocation getReturnValue:&value]; 346 return valueInternalValue(value); 347 } 348 349 RetainPtr<NSInvocation> m_conversionInvocation; 350 StructBuffer m_buffer; 351 }; 352 353 class ResultTypeDelegate final { 354 public: 355 typedef std::unique_ptr<CallbackResult> ResultType; 356 357 template<typename T> 358 static ResultType typeInteger() 359 { 360 return makeUnique<CallbackResultNumeric<T>>(); 361 } 362 363 template<typename T> 364 static ResultType typeDouble() 365 { 366 return makeUnique<CallbackResultNumeric<T>>(); 367 } 368 369 static ResultType typeBool() 370 { 371 return makeUnique<CallbackResultBoolean>(); 372 } 373 374 static ResultType typeVoid() 375 { 376 return makeUnique<CallbackResultVoid>(); 377 } 378 379 static ResultType typeId() 380 { 381 return makeUnique<CallbackResultId>(); 382 } 383 384 static ResultType typeOfClass(const char*, const char*) 385 { 386 return makeUnique<CallbackResultId>(); 387 } 388 389 static ResultType typeBlock(const char*, const char*) 390 { 391 return makeUnique<CallbackResultId>(); 392 } 393 394 static ResultType typeStruct(const char* begin, const char* end) 395 { 396 StringRange copy(begin, end); 397 if (NSInvocation *invocation = typeToValueInvocationFor(copy)) 398 return makeUnique<CallbackResultStruct>(invocation, copy); 399 return nullptr; 400 } 401 }; 402 403 enum CallbackType { 404 CallbackInitMethod, 405 CallbackInstanceMethod, 406 CallbackClassMethod, 407 CallbackBlock 408 }; 409 410 namespace JSC { 411 412 class ObjCCallbackFunctionImpl final { 413 WTF_MAKE_FAST_ALLOCATED; 414 public: 415 ObjCCallbackFunctionImpl(NSInvocation *invocation, CallbackType type, Class instanceClass, std::unique_ptr<CallbackArgument> arguments, std::unique_ptr<CallbackResult> result) 416 : m_type(type) 417 , m_instanceClass(instanceClass) 418 , m_invocation(invocation) 419 , m_arguments(WTFMove(arguments)) 420 , m_result(WTFMove(result)) 421 { 422 ASSERT((type != CallbackInstanceMethod && type != CallbackInitMethod) || instanceClass); 423 } 424 425 void destroy(Heap& heap) 426 { 427 // We need to explicitly release the target since we didn't call 428 // -retainArguments on m_invocation (and we don't want to do so). 429 if (m_type == CallbackBlock || m_type == CallbackClassMethod) 430 heap.releaseSoon(adoptNS([m_invocation.get() target])); 431 m_instanceClass = nil; 432 } 433 434 JSValueRef call(JSContext *context, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); 435 436 id wrappedBlock() 437 { 438 return m_type == CallbackBlock ? [m_invocation target] : nil; 439 } 440 441 id wrappedConstructor() 442 { 443 switch (m_type) { 444 case CallbackBlock: 445 return [m_invocation target]; 446 case CallbackInitMethod: 447 return m_instanceClass.get(); 448 default: 449 return nil; 450 } 451 } 452 453 CallbackType type() const { return m_type; } 454 455 bool isConstructible() 456 { 457 return !!wrappedBlock() || m_type == CallbackInitMethod; 458 } 459 460 String name(); 461 462 private: 463 CallbackType m_type; 464 RetainPtr<Class> m_instanceClass; 465 RetainPtr<NSInvocation> m_invocation; 466 std::unique_ptr<CallbackArgument> m_arguments; 467 std::unique_ptr<CallbackResult> m_result; 468 }; 469 470 static JSValueRef objCCallbackFunctionCallAsFunction(JSContextRef callerContext, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) 471 { 472 ASSERT(exception && !*exception); 473 474 // Retake the API lock - we need this for a few reasons: 475 // (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. 476 // (2) We're calling some JSC internals that require us to be on the 'inside' - e.g. createTypeError. 477 // (3) We need to be locked (per context would be fine) against conflicting usage of the ObjCCallbackFunction's NSInvocation. 478 JSC::JSLockHolder locker(toJS(callerContext)); 479 480 ObjCCallbackFunction* callback = static_cast<ObjCCallbackFunction*>(toJS(function)); 481 ObjCCallbackFunctionImpl* impl = callback->impl(); 482 JSContext *context = [JSContext contextWithJSGlobalContextRef:toGlobalRef(callback->globalObject())]; 483 484 if (impl->type() == CallbackInitMethod) { 485 JSGlobalContextRef contextRef = [context JSGlobalContextRef]; 486 *exception = toRef(JSC::createTypeError(toJS(contextRef), "Cannot call a class constructor without |new|"_s)); 487 if (*exception) 488 return nullptr; 489 return JSValueMakeUndefined(contextRef); 490 } 491 492 CallbackData callbackData; 493 JSValueRef result; 494 @autoreleasepool { 495 [context beginCallbackWithData:&callbackData calleeValue:function thisValue:thisObject argumentCount:argumentCount arguments:arguments]; 496 result = impl->call(context, thisObject, argumentCount, arguments, exception); 497 if (context.exception) 498 *exception = valueInternalValue(context.exception); 499 [context endCallbackWithData:&callbackData]; 500 } 501 if (*exception) 502 return nullptr; 503 return result; 504 } 505 506 static JSObjectRef objCCallbackFunctionCallAsConstructor(JSContextRef callerContext, JSObjectRef constructor, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) 507 { 508 ASSERT(exception && !*exception); 509 JSC::JSLockHolder locker(toJS(callerContext)); 510 511 ObjCCallbackFunction* callback = static_cast<ObjCCallbackFunction*>(toJS(constructor)); 512 ObjCCallbackFunctionImpl* impl = callback->impl(); 513 JSContext *context = [JSContext contextWithJSGlobalContextRef:toGlobalRef(toJS(callerContext))]; 514 515 CallbackData callbackData; 516 JSValueRef result; 517 @autoreleasepool { 518 [context beginCallbackWithData:&callbackData calleeValue:constructor thisValue:nullptr argumentCount:argumentCount arguments:arguments]; 519 result = impl->call(context, nullptr, argumentCount, arguments, exception); 520 if (context.exception) 521 *exception = valueInternalValue(context.exception); 522 [context endCallbackWithData:&callbackData]; 523 } 524 if (*exception) 525 return nullptr; 526 527 JSGlobalContextRef contextRef = [context JSGlobalContextRef]; 528 if (!JSValueIsObject(contextRef, result)) { 529 *exception = toRef(JSC::createTypeError(toJS(contextRef), "Objective-C blocks called as constructors must return an object."_s)); 530 return nullptr; 531 } 532 ASSERT(!*exception); 533 return const_cast<JSObjectRef>(result); 534 } 535 536 const JSC::ClassInfo ObjCCallbackFunction::s_info = { "CallbackFunction", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(ObjCCallbackFunction) }; 537 538 static JSC_DECLARE_HOST_FUNCTION(callObjCCallbackFunction); 539 static JSC_DECLARE_HOST_FUNCTION(constructObjCCallbackFunction); 540 541 JSC_DEFINE_HOST_FUNCTION(callObjCCallbackFunction, (JSGlobalObject* globalObject, CallFrame* callFrame)) 542 { 543 return APICallbackFunction::callImpl<ObjCCallbackFunction>(globalObject, callFrame); 544 } 545 546 JSC_DEFINE_HOST_FUNCTION(constructObjCCallbackFunction, (JSGlobalObject* globalObject, CallFrame* callFrame)) 547 { 548 return APICallbackFunction::constructImpl<ObjCCallbackFunction>(globalObject, callFrame); 549 } 550 551 ObjCCallbackFunction::ObjCCallbackFunction(JSC::VM& vm, JSC::Structure* structure, JSObjectCallAsFunctionCallback functionCallback, JSObjectCallAsConstructorCallback constructCallback, std::unique_ptr<ObjCCallbackFunctionImpl> impl) 552 : Base(vm, structure, callObjCCallbackFunction, impl->isConstructible() ? constructObjCCallbackFunction : nullptr) 553 , m_functionCallback(functionCallback) 554 , m_constructCallback(constructCallback) 555 , m_impl(WTFMove(impl)) 556 { 557 } 558 559 ObjCCallbackFunction* ObjCCallbackFunction::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, const String& name, std::unique_ptr<ObjCCallbackFunctionImpl> impl) 560 { 561 Structure* structure = globalObject->objcCallbackFunctionStructure(); 562 ObjCCallbackFunction* function = new (NotNull, allocateCell<ObjCCallbackFunction>(vm.heap)) ObjCCallbackFunction(vm, structure, objCCallbackFunctionCallAsFunction, objCCallbackFunctionCallAsConstructor, WTFMove(impl)); 563 function->finishCreation(vm, 0, name); 564 return function; 565 } 566 567 void ObjCCallbackFunction::destroy(JSCell* cell) 568 { 569 ObjCCallbackFunction& function = *static_cast<ObjCCallbackFunction*>(cell); 570 function.impl()->destroy(*Heap::heap(cell)); 571 function.~ObjCCallbackFunction(); 572 } 573 574 String ObjCCallbackFunctionImpl::name() 575 { 576 if (m_type == CallbackInitMethod) 577 return class_getName(m_instanceClass.get()); 578 // FIXME: Maybe we could support having the selector as the name of the non-init 579 // functions to make it a bit more user-friendly from the JS side? 580 return ""; 581 } 582 583 JSValueRef ObjCCallbackFunctionImpl::call(JSContext *context, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) 584 { 585 ASSERT(exception && !*exception); 586 JSGlobalContextRef contextRef = [context JSGlobalContextRef]; 587 588 id target; 589 size_t firstArgument; 590 switch (m_type) { 591 case CallbackInitMethod: { 592 RELEASE_ASSERT(!thisObject); 593 target = [m_instanceClass alloc]; 594 if (!target || ![target isKindOfClass:m_instanceClass.get()]) { 595 *exception = toRef(JSC::createTypeError(toJS(contextRef), "self type check failed for Objective-C instance method"_s)); 596 if (*exception) 597 return nullptr; 598 return JSValueMakeUndefined(contextRef); 599 } 600 [m_invocation setTarget:target]; 601 firstArgument = 2; 602 break; 603 } 604 case CallbackInstanceMethod: { 605 target = tryUnwrapObjcObject(contextRef, thisObject); 606 if (!target || ![target isKindOfClass:m_instanceClass.get()]) { 607 *exception = toRef(JSC::createTypeError(toJS(contextRef), "self type check failed for Objective-C instance method"_s)); 608 if (*exception) 609 return nullptr; 610 return JSValueMakeUndefined(contextRef); 611 } 612 [m_invocation setTarget:target]; 613 firstArgument = 2; 614 break; 615 } 616 case CallbackClassMethod: 617 firstArgument = 2; 618 break; 619 case CallbackBlock: 620 firstArgument = 1; 621 } 622 623 size_t argumentNumber = 0; 624 for (CallbackArgument* argument = m_arguments.get(); argument; argument = argument->m_next.get()) { 625 JSValueRef value = argumentNumber < argumentCount ? arguments[argumentNumber] : JSValueMakeUndefined(contextRef); 626 argument->set(m_invocation.get(), argumentNumber + firstArgument, context, value, exception); 627 if (*exception) 628 return nullptr; 629 ++argumentNumber; 630 } 631 632 [m_invocation invoke]; 633 634 JSValueRef result = m_result->get(m_invocation.get(), context, exception); 635 if (*exception) 636 return nullptr; 637 638 // Balance our call to -alloc with a call to -autorelease. We have to do this after calling -init 639 // because init family methods are allowed to release the allocated object and return something 640 // else in its place. 641 if (m_type == CallbackInitMethod) { 642 id objcResult = tryUnwrapObjcObject(contextRef, result); 643 if (objcResult) 644 [objcResult autorelease]; 645 } 646 647 return result; 648 } 649 650 } // namespace JSC 651 652 static bool blockSignatureContainsClass() 653 { 654 static bool containsClass = ^{ 655 id block = ^(NSString *string){ return string; }; 656 return _Block_has_signature((__bridge void*)block) && strstr(_Block_signature((__bridge void*)block), "NSString"); 657 }(); 658 return containsClass; 659 } 660 661 static inline bool skipNumber(const char*& position) 662 { 663 if (!isASCIIDigit(*position)) 664 return false; 665 while (isASCIIDigit(*++position)) { } 666 return true; 667 } 668 669 static JSObjectRef objCCallbackFunctionForInvocation(JSContext *context, NSInvocation *invocation, CallbackType type, Class instanceClass, const char* signatureWithObjcClasses) 670 { 671 if (!signatureWithObjcClasses) 672 return nullptr; 673 674 const char* position = signatureWithObjcClasses; 675 676 auto result = parseObjCType<ResultTypeDelegate>(position); 677 if (!result || !skipNumber(position)) 678 return nullptr; 679 680 switch (type) { 681 case CallbackInitMethod: 682 case CallbackInstanceMethod: 683 case CallbackClassMethod: 684 // Methods are passed two implicit arguments - (id)self, and the selector. 685 if ('@' != *position++ || !skipNumber(position) || ':' != *position++ || !skipNumber(position)) 686 return nullptr; 687 break; 688 case CallbackBlock: 689 // Blocks are passed one implicit argument - the block, of type "@?". 690 if (('@' != *position++) || ('?' != *position++) || !skipNumber(position)) 691 return nullptr; 692 // Only allow arguments of type 'id' if the block signature contains the NS type information. 693 if ((!blockSignatureContainsClass() && strchr(position, '@'))) 694 return nullptr; 695 break; 696 } 697 698 std::unique_ptr<CallbackArgument> arguments; 699 auto* nextArgument = &arguments; 700 unsigned argumentCount = 0; 701 while (*position) { 702 auto argument = parseObjCType<ArgumentTypeDelegate>(position); 703 if (!argument || !skipNumber(position)) 704 return nullptr; 705 706 *nextArgument = WTFMove(argument); 707 nextArgument = &(*nextArgument)->m_next; 708 ++argumentCount; 709 } 710 711 JSC::JSGlobalObject* globalObject = toJS([context JSGlobalContextRef]); 712 JSC::VM& vm = globalObject->vm(); 713 JSC::JSLockHolder locker(vm); 714 auto impl = makeUnique<JSC::ObjCCallbackFunctionImpl>(invocation, type, instanceClass, WTFMove(arguments), WTFMove(result)); 715 const String& name = impl->name(); 716 return toRef(JSC::ObjCCallbackFunction::create(vm, globalObject, name, WTFMove(impl))); 717 } 718 719 JSObjectRef objCCallbackFunctionForInit(JSContext *context, Class cls, Protocol *protocol, SEL sel, const char* types) 720 { 721 NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:types]]; 722 [invocation setSelector:sel]; 723 return objCCallbackFunctionForInvocation(context, invocation, CallbackInitMethod, cls, _protocol_getMethodTypeEncoding(protocol, sel, YES, YES)); 724 } 725 726 JSObjectRef objCCallbackFunctionForMethod(JSContext *context, Class cls, Protocol *protocol, BOOL isInstanceMethod, SEL sel, const char* types) 727 { 728 NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:types]]; 729 [invocation setSelector:sel]; 730 if (!isInstanceMethod) { 731 [invocation setTarget:cls]; 732 // We need to retain the target Class because m_invocation doesn't retain it by default (and we don't want it to). 733 // FIXME: What releases it? 734 CFRetain((__bridge CFTypeRef)cls); 735 } 736 return objCCallbackFunctionForInvocation(context, invocation, isInstanceMethod ? CallbackInstanceMethod : CallbackClassMethod, isInstanceMethod ? cls : nil, _protocol_getMethodTypeEncoding(protocol, sel, YES, isInstanceMethod)); 737 } 738 739 JSObjectRef objCCallbackFunctionForBlock(JSContext *context, id target) 740 { 741 if (!_Block_has_signature((__bridge void*)target)) 742 return nullptr; 743 const char* signature = _Block_signature((__bridge void*)target); 744 NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:signature]]; 745 746 // We don't want to use -retainArguments because that leaks memory. Arguments 747 // would be retained indefinitely between invocations of the callback. 748 // Additionally, we copy the target because we want the block to stick around 749 // until the ObjCCallbackFunctionImpl is destroyed. 750 [invocation setTarget:[target copy]]; 751 752 return objCCallbackFunctionForInvocation(context, invocation, CallbackBlock, nil, signature); 753 } 754 755 id tryUnwrapConstructor(JSC::VM* vm, JSObjectRef object) 756 { 757 if (!toJS(object)->inherits<JSC::ObjCCallbackFunction>(*vm)) 758 return nil; 759 JSC::ObjCCallbackFunctionImpl* impl = static_cast<JSC::ObjCCallbackFunction*>(toJS(object))->impl(); 760 if (!impl->isConstructible()) 761 return nil; 762 return impl->wrappedConstructor(); 763 } 764 765 #endif