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