1/* 2 * Copyright (C) 2013 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#include "config.h" 27#import "JavaScriptCore.h" 28 29#if JSC_OBJC_API_ENABLED 30 31#import "APICast.h" 32#import "APIShims.h" 33#import "Error.h" 34#import "JSCJSValueInlines.h" 35#import "JSCell.h" 36#import "JSCellInlines.h" 37#import "JSContextInternal.h" 38#import "JSWrapperMap.h" 39#import "JSValueInternal.h" 40#import "ObjCCallbackFunction.h" 41#import "ObjcRuntimeExtras.h" 42#import <objc/runtime.h> 43#import <wtf/RetainPtr.h> 44 45class CallbackArgument { 46public: 47 virtual ~CallbackArgument(); 48 virtual void set(NSInvocation *, NSInteger, JSContext *, JSValueRef, JSValueRef*) = 0; 49 50 OwnPtr<CallbackArgument> m_next; 51}; 52 53CallbackArgument::~CallbackArgument() 54{ 55} 56 57class CallbackArgumentBoolean : public CallbackArgument { 58 virtual void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef*) override 59 { 60 bool value = JSValueToBoolean([context JSGlobalContextRef], argument); 61 [invocation setArgument:&value atIndex:argumentNumber]; 62 } 63}; 64 65template<typename T> 66class CallbackArgumentInteger : public CallbackArgument { 67 virtual void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) override 68 { 69 T value = (T)JSC::toInt32(JSValueToNumber([context JSGlobalContextRef], argument, exception)); 70 [invocation setArgument:&value atIndex:argumentNumber]; 71 } 72}; 73 74template<typename T> 75class CallbackArgumentDouble : public CallbackArgument { 76 virtual void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) override 77 { 78 T value = (T)JSValueToNumber([context JSGlobalContextRef], argument, exception); 79 [invocation setArgument:&value atIndex:argumentNumber]; 80 } 81}; 82 83class CallbackArgumentJSValue : public CallbackArgument { 84 virtual void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef*) override 85 { 86 JSValue *value = [JSValue valueWithJSValueRef:argument inContext:context]; 87 [invocation setArgument:&value atIndex:argumentNumber]; 88 } 89}; 90 91class CallbackArgumentId : public CallbackArgument { 92 virtual void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef*) override 93 { 94 id value = valueToObject(context, argument); 95 [invocation setArgument:&value atIndex:argumentNumber]; 96 } 97}; 98 99class CallbackArgumentOfClass : public CallbackArgument { 100public: 101 CallbackArgumentOfClass(Class cls) 102 : CallbackArgument() 103 , m_class(cls) 104 { 105 [m_class retain]; 106 } 107 108private: 109 virtual ~CallbackArgumentOfClass() 110 { 111 [m_class release]; 112 } 113 114 virtual void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) override 115 { 116 JSGlobalContextRef contextRef = [context JSGlobalContextRef]; 117 118 id object = tryUnwrapObjcObject(contextRef, argument); 119 if (object && [object isKindOfClass:m_class]) { 120 [invocation setArgument:&object atIndex:argumentNumber]; 121 return; 122 } 123 124 if (JSValueIsNull(contextRef, argument) || JSValueIsUndefined(contextRef, argument)) { 125 object = nil; 126 [invocation setArgument:&object atIndex:argumentNumber]; 127 return; 128 } 129 130 *exception = toRef(JSC::createTypeError(toJS(contextRef), "Argument does not match Objective-C Class")); 131 } 132 133 Class m_class; 134}; 135 136class CallbackArgumentNSNumber : public CallbackArgument { 137 virtual void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) override 138 { 139 id value = valueToNumber([context JSGlobalContextRef], argument, exception); 140 [invocation setArgument:&value atIndex:argumentNumber]; 141 } 142}; 143 144class CallbackArgumentNSString : public CallbackArgument { 145 virtual void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) override 146 { 147 id value = valueToString([context JSGlobalContextRef], argument, exception); 148 [invocation setArgument:&value atIndex:argumentNumber]; 149 } 150}; 151 152class CallbackArgumentNSDate : public CallbackArgument { 153 virtual void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) override 154 { 155 id value = valueToDate([context JSGlobalContextRef], argument, exception); 156 [invocation setArgument:&value atIndex:argumentNumber]; 157 } 158}; 159 160class CallbackArgumentNSArray : public CallbackArgument { 161 virtual void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) override 162 { 163 id value = valueToArray([context JSGlobalContextRef], argument, exception); 164 [invocation setArgument:&value atIndex:argumentNumber]; 165 } 166}; 167 168class CallbackArgumentNSDictionary : public CallbackArgument { 169 virtual void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) override 170 { 171 id value = valueToDictionary([context JSGlobalContextRef], argument, exception); 172 [invocation setArgument:&value atIndex:argumentNumber]; 173 } 174}; 175 176class CallbackArgumentStruct : public CallbackArgument { 177public: 178 CallbackArgumentStruct(NSInvocation *conversionInvocation, const char* encodedType) 179 : m_conversionInvocation(conversionInvocation) 180 , m_buffer(encodedType) 181 { 182 } 183 184private: 185 virtual void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef*) override 186 { 187 JSValue *value = [JSValue valueWithJSValueRef:argument inContext:context]; 188 [m_conversionInvocation invokeWithTarget:value]; 189 [m_conversionInvocation getReturnValue:m_buffer]; 190 [invocation setArgument:m_buffer atIndex:argumentNumber]; 191 } 192 193 RetainPtr<NSInvocation> m_conversionInvocation; 194 StructBuffer m_buffer; 195}; 196 197class ArgumentTypeDelegate { 198public: 199 typedef CallbackArgument* ResultType; 200 201 template<typename T> 202 static ResultType typeInteger() 203 { 204 return new CallbackArgumentInteger<T>; 205 } 206 207 template<typename T> 208 static ResultType typeDouble() 209 { 210 return new CallbackArgumentDouble<T>; 211 } 212 213 static ResultType typeBool() 214 { 215 return new CallbackArgumentBoolean; 216 } 217 218 static ResultType typeVoid() 219 { 220 RELEASE_ASSERT_NOT_REACHED(); 221 return 0; 222 } 223 224 static ResultType typeId() 225 { 226 return new CallbackArgumentId; 227 } 228 229 static ResultType typeOfClass(const char* begin, const char* end) 230 { 231 StringRange copy(begin, end); 232 Class cls = objc_getClass(copy); 233 if (!cls) 234 return 0; 235 236 if (cls == [JSValue class]) 237 return new CallbackArgumentJSValue; 238 if (cls == [NSString class]) 239 return new CallbackArgumentNSString; 240 if (cls == [NSNumber class]) 241 return new CallbackArgumentNSNumber; 242 if (cls == [NSDate class]) 243 return new CallbackArgumentNSDate; 244 if (cls == [NSArray class]) 245 return new CallbackArgumentNSArray; 246 if (cls == [NSDictionary class]) 247 return new CallbackArgumentNSDictionary; 248 249 return new CallbackArgumentOfClass(cls); 250 } 251 252 static ResultType typeBlock(const char*, const char*) 253 { 254 return nil; 255 } 256 257 static ResultType typeStruct(const char* begin, const char* end) 258 { 259 StringRange copy(begin, end); 260 if (NSInvocation *invocation = valueToTypeInvocationFor(copy)) 261 return new CallbackArgumentStruct(invocation, copy); 262 return 0; 263 } 264}; 265 266class CallbackResult { 267public: 268 virtual ~CallbackResult() 269 { 270 } 271 272 virtual JSValueRef get(NSInvocation *, JSContext *, JSValueRef*) = 0; 273}; 274 275class CallbackResultVoid : public CallbackResult { 276 virtual JSValueRef get(NSInvocation *, JSContext *context, JSValueRef*) override 277 { 278 return JSValueMakeUndefined([context JSGlobalContextRef]); 279 } 280}; 281 282class CallbackResultId : public CallbackResult { 283 virtual JSValueRef get(NSInvocation *invocation, JSContext *context, JSValueRef*) override 284 { 285 id value; 286 [invocation getReturnValue:&value]; 287 return objectToValue(context, value); 288 } 289}; 290 291template<typename T> 292class CallbackResultNumeric : public CallbackResult { 293 virtual JSValueRef get(NSInvocation *invocation, JSContext *context, JSValueRef*) override 294 { 295 T value; 296 [invocation getReturnValue:&value]; 297 return JSValueMakeNumber([context JSGlobalContextRef], value); 298 } 299}; 300 301class CallbackResultBoolean : public CallbackResult { 302 virtual JSValueRef get(NSInvocation *invocation, JSContext *context, JSValueRef*) override 303 { 304 bool value; 305 [invocation getReturnValue:&value]; 306 return JSValueMakeBoolean([context JSGlobalContextRef], value); 307 } 308}; 309 310class CallbackResultStruct : public CallbackResult { 311public: 312 CallbackResultStruct(NSInvocation *conversionInvocation, const char* encodedType) 313 : m_conversionInvocation(conversionInvocation) 314 , m_buffer(encodedType) 315 { 316 } 317 318private: 319 virtual JSValueRef get(NSInvocation *invocation, JSContext *context, JSValueRef*) override 320 { 321 [invocation getReturnValue:m_buffer]; 322 323 [m_conversionInvocation setArgument:m_buffer atIndex:2]; 324 [m_conversionInvocation setArgument:&context atIndex:3]; 325 [m_conversionInvocation invokeWithTarget:[JSValue class]]; 326 327 JSValue *value; 328 [m_conversionInvocation getReturnValue:&value]; 329 return valueInternalValue(value); 330 } 331 332 RetainPtr<NSInvocation> m_conversionInvocation; 333 StructBuffer m_buffer; 334}; 335 336class ResultTypeDelegate { 337public: 338 typedef CallbackResult* ResultType; 339 340 template<typename T> 341 static ResultType typeInteger() 342 { 343 return new CallbackResultNumeric<T>; 344 } 345 346 template<typename T> 347 static ResultType typeDouble() 348 { 349 return new CallbackResultNumeric<T>; 350 } 351 352 static ResultType typeBool() 353 { 354 return new CallbackResultBoolean; 355 } 356 357 static ResultType typeVoid() 358 { 359 return new CallbackResultVoid; 360 } 361 362 static ResultType typeId() 363 { 364 return new CallbackResultId(); 365 } 366 367 static ResultType typeOfClass(const char*, const char*) 368 { 369 return new CallbackResultId(); 370 } 371 372 static ResultType typeBlock(const char*, const char*) 373 { 374 return new CallbackResultId(); 375 } 376 377 static ResultType typeStruct(const char* begin, const char* end) 378 { 379 StringRange copy(begin, end); 380 if (NSInvocation *invocation = typeToValueInvocationFor(copy)) 381 return new CallbackResultStruct(invocation, copy); 382 return 0; 383 } 384}; 385 386enum CallbackType { 387 CallbackInstanceMethod, 388 CallbackClassMethod, 389 CallbackBlock 390}; 391 392namespace JSC { 393 394class ObjCCallbackFunctionImpl { 395public: 396 ObjCCallbackFunctionImpl(JSContext *context, NSInvocation *invocation, CallbackType type, Class instanceClass, PassOwnPtr<CallbackArgument> arguments, PassOwnPtr<CallbackResult> result) 397 : m_context(context) 398 , m_type(type) 399 , m_instanceClass([instanceClass retain]) 400 , m_invocation(invocation) 401 , m_arguments(arguments) 402 , m_result(result) 403 { 404 ASSERT(type != CallbackInstanceMethod || instanceClass); 405 } 406 407 ~ObjCCallbackFunctionImpl() 408 { 409 [m_instanceClass release]; 410 } 411 412 JSValueRef call(JSContext *context, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); 413 414 JSContext *context() 415 { 416 return m_context.get(); 417 } 418 419 void setContext(JSContext *context) 420 { 421 ASSERT(!m_context.get()); 422 m_context.set(context); 423 } 424 425 id wrappedBlock() 426 { 427 return m_type == CallbackBlock ? [m_invocation target] : nil; 428 } 429 430private: 431 WeakContextRef m_context; 432 CallbackType m_type; 433 Class m_instanceClass; 434 RetainPtr<NSInvocation> m_invocation; 435 OwnPtr<CallbackArgument> m_arguments; 436 OwnPtr<CallbackResult> m_result; 437}; 438 439static JSValueRef objCCallbackFunctionCallAsFunction(JSContextRef callerContext, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) 440{ 441 // Retake the API lock - we need this for a few reasons: 442 // (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. 443 // (2) We're calling some JSC internals that require us to be on the 'inside' - e.g. createTypeError. 444 // (3) We need to be locked (per context would be fine) against conflicting usage of the ObjCCallbackFunction's NSInvocation. 445 JSC::APIEntryShim entryShim(toJS(callerContext)); 446 447 ObjCCallbackFunction* callback = static_cast<ObjCCallbackFunction*>(toJS(function)); 448 ObjCCallbackFunctionImpl* impl = callback->impl(); 449 JSContext *context = impl->context(); 450 if (!context) { 451 context = [JSContext contextWithJSGlobalContextRef:toGlobalRef(toJS(callerContext)->lexicalGlobalObject()->globalExec())]; 452 impl->setContext(context); 453 } 454 455 CallbackData callbackData; 456 JSValueRef result; 457 @autoreleasepool { 458 [context beginCallbackWithData:&callbackData thisValue:thisObject argumentCount:argumentCount arguments:arguments]; 459 result = impl->call(context, thisObject, argumentCount, arguments, exception); 460 if (context.exception) 461 *exception = valueInternalValue(context.exception); 462 [context endCallbackWithData:&callbackData]; 463 } 464 return result; 465} 466 467const JSC::ClassInfo ObjCCallbackFunction::s_info = { "CallbackFunction", &Base::s_info, 0, 0, CREATE_METHOD_TABLE(ObjCCallbackFunction) }; 468 469ObjCCallbackFunction::ObjCCallbackFunction(JSC::JSGlobalObject* globalObject, JSObjectCallAsFunctionCallback callback, PassOwnPtr<ObjCCallbackFunctionImpl> impl) 470 : Base(globalObject, globalObject->objcCallbackFunctionStructure(), callback) 471 , m_impl(impl) 472{ 473} 474 475ObjCCallbackFunction* ObjCCallbackFunction::create(JSC::ExecState* exec, JSC::JSGlobalObject* globalObject, const String& name, PassOwnPtr<ObjCCallbackFunctionImpl> impl) 476{ 477 ObjCCallbackFunction* function = new (NotNull, allocateCell<ObjCCallbackFunction>(*exec->heap())) ObjCCallbackFunction(globalObject, objCCallbackFunctionCallAsFunction, impl); 478 function->finishCreation(exec->vm(), name); 479 return function; 480} 481 482void ObjCCallbackFunction::destroy(JSCell* cell) 483{ 484 static_cast<ObjCCallbackFunction*>(cell)->ObjCCallbackFunction::~ObjCCallbackFunction(); 485} 486 487JSValueRef ObjCCallbackFunctionImpl::call(JSContext *context, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) 488{ 489 JSGlobalContextRef contextRef = [context JSGlobalContextRef]; 490 491 size_t firstArgument; 492 switch (m_type) { 493 case CallbackInstanceMethod: { 494 id target = tryUnwrapObjcObject(contextRef, thisObject); 495 if (!target || ![target isKindOfClass:m_instanceClass]) { 496 *exception = toRef(JSC::createTypeError(toJS(contextRef), "self type check failed for Objective-C instance method")); 497 return JSValueMakeUndefined(contextRef); 498 } 499 [m_invocation setTarget:target]; 500 } 501 // fallthrough - firstArgument for CallbackInstanceMethod is also 2! 502 case CallbackClassMethod: 503 firstArgument = 2; 504 break; 505 case CallbackBlock: 506 firstArgument = 1; 507 } 508 509 size_t argumentNumber = 0; 510 for (CallbackArgument* argument = m_arguments.get(); argument; argument = argument->m_next.get()) { 511 JSValueRef value = argumentNumber < argumentCount ? arguments[argumentNumber] : JSValueMakeUndefined(contextRef); 512 argument->set(m_invocation.get(), argumentNumber + firstArgument, context, value, exception); 513 if (*exception) 514 return JSValueMakeUndefined(contextRef); 515 ++argumentNumber; 516 } 517 518 [m_invocation invoke]; 519 520 return m_result->get(m_invocation.get(), context, exception); 521} 522 523} // namespace JSC 524 525static bool blockSignatureContainsClass() 526{ 527 static bool containsClass = ^{ 528 id block = ^(NSString *string){ return string; }; 529 return _Block_has_signature(block) && strstr(_Block_signature(block), "NSString"); 530 }(); 531 return containsClass; 532} 533 534inline bool skipNumber(const char*& position) 535{ 536 if (!isASCIIDigit(*position)) 537 return false; 538 while (isASCIIDigit(*++position)) { } 539 return true; 540} 541 542static JSObjectRef objCCallbackFunctionForInvocation(JSContext *context, NSInvocation *invocation, CallbackType type, Class instanceClass, const char* signatureWithObjcClasses) 543{ 544 const char* position = signatureWithObjcClasses; 545 546 OwnPtr<CallbackResult> result = adoptPtr(parseObjCType<ResultTypeDelegate>(position)); 547 if (!result || !skipNumber(position)) 548 return nil; 549 550 switch (type) { 551 case CallbackInstanceMethod: 552 case CallbackClassMethod: 553 // Methods are passed two implicit arguments - (id)self, and the selector. 554 if ('@' != *position++ || !skipNumber(position) || ':' != *position++ || !skipNumber(position)) 555 return nil; 556 break; 557 case CallbackBlock: 558 // Blocks are passed one implicit argument - the block, of type "@?". 559 if (('@' != *position++) || ('?' != *position++) || !skipNumber(position)) 560 return nil; 561 // Only allow arguments of type 'id' if the block signature contains the NS type information. 562 if ((!blockSignatureContainsClass() && strchr(position, '@'))) 563 return nil; 564 break; 565 } 566 567 OwnPtr<CallbackArgument> arguments = 0; 568 OwnPtr<CallbackArgument>* nextArgument = &arguments; 569 unsigned argumentCount = 0; 570 while (*position) { 571 OwnPtr<CallbackArgument> argument = adoptPtr(parseObjCType<ArgumentTypeDelegate>(position)); 572 if (!argument || !skipNumber(position)) 573 return nil; 574 575 *nextArgument = argument.release(); 576 nextArgument = &(*nextArgument)->m_next; 577 ++argumentCount; 578 } 579 580 JSC::ExecState* exec = toJS([context JSGlobalContextRef]); 581 JSC::APIEntryShim shim(exec); 582 OwnPtr<JSC::ObjCCallbackFunctionImpl> impl = adoptPtr(new JSC::ObjCCallbackFunctionImpl(context, invocation, type, instanceClass, arguments.release(), result.release())); 583 // 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? 584 return toRef(JSC::ObjCCallbackFunction::create(exec, exec->lexicalGlobalObject(), "", impl.release())); 585} 586 587JSObjectRef objCCallbackFunctionForMethod(JSContext *context, Class cls, Protocol *protocol, BOOL isInstanceMethod, SEL sel, const char* types) 588{ 589 NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:types]]; 590 [invocation setSelector:sel]; 591 if (!isInstanceMethod) 592 [invocation setTarget:cls]; 593 return objCCallbackFunctionForInvocation(context, invocation, isInstanceMethod ? CallbackInstanceMethod : CallbackClassMethod, isInstanceMethod ? cls : nil, _protocol_getMethodTypeEncoding(protocol, sel, YES, isInstanceMethod)); 594} 595 596JSObjectRef objCCallbackFunctionForBlock(JSContext *context, id target) 597{ 598 if (!_Block_has_signature(target)) 599 return 0; 600 const char* signature = _Block_signature(target); 601 NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:signature]]; 602 [invocation retainArguments]; 603 id targetCopy = [target copy]; 604 [invocation setTarget:targetCopy]; 605 [targetCopy release]; 606 return objCCallbackFunctionForInvocation(context, invocation, CallbackBlock, nil, signature); 607} 608 609id tryUnwrapBlock(JSObjectRef object) 610{ 611 if (!toJS(object)->inherits(&JSC::ObjCCallbackFunction::s_info)) 612 return nil; 613 return static_cast<JSC::ObjCCallbackFunction*>(toJS(object))->impl()->wrappedBlock(); 614} 615 616#endif 617