/ API / ObjCCallbackFunction.mm
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