1/*
2 * Copyright (C) 2004, 2008, 2009 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 COMPUTER, 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 COMPUTER, 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 "objc_instance.h"
28
29#import "runtime_method.h"
30#import <runtime/ObjectPrototype.h>
31#import "JSDOMBinding.h"
32#import "ObjCRuntimeObject.h"
33#import "WebScriptObject.h"
34#import <objc/objc-auto.h>
35#import <runtime/Error.h>
36#import <runtime/JSLock.h>
37#import "runtime/FunctionPrototype.h"
38#import <wtf/Assertions.h>
39
40#ifdef NDEBUG
41#define OBJC_LOG(formatAndArgs...) ((void)0)
42#else
43#define OBJC_LOG(formatAndArgs...) { \
44    fprintf (stderr, "%s:%d -- %s:  ", __FILE__, __LINE__, __FUNCTION__); \
45    fprintf(stderr, formatAndArgs); \
46}
47#endif
48
49using namespace JSC::Bindings;
50using namespace JSC;
51
52static NSString *s_exception;
53static JSGlobalObject* s_exceptionEnvironment; // No need to protect this value, since we just use it for a pointer comparison.
54static NSMapTable *s_instanceWrapperCache;
55
56#if COMPILER(CLANG)
57#pragma clang diagnostic push
58#pragma clang diagnostic ignored "-Wdeprecated-declarations"
59#endif
60
61static NSMapTable *createInstanceWrapperCache()
62{
63    // NSMapTable with zeroing weak pointers is the recommended way to build caches like this under garbage collection.
64    NSPointerFunctionsOptions keyOptions = NSPointerFunctionsZeroingWeakMemory | NSPointerFunctionsOpaquePersonality;
65    NSPointerFunctionsOptions valueOptions = NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality;
66    return [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:valueOptions capacity:0];
67}
68
69#if COMPILER(CLANG)
70#pragma clang diagnostic pop
71#endif
72
73RuntimeObject* ObjcInstance::newRuntimeObject(ExecState* exec)
74{
75    return ObjCRuntimeObject::create(exec, exec->lexicalGlobalObject(), this);
76}
77
78void ObjcInstance::setGlobalException(NSString* exception, JSGlobalObject* exceptionEnvironment)
79{
80    NSString *oldException = s_exception;
81    s_exception = [exception copy];
82    [oldException release];
83
84    s_exceptionEnvironment = exceptionEnvironment;
85}
86
87void ObjcInstance::moveGlobalExceptionToExecState(ExecState* exec)
88{
89    if (!s_exception) {
90        ASSERT(!s_exceptionEnvironment);
91        return;
92    }
93
94    if (!s_exceptionEnvironment || s_exceptionEnvironment == exec->dynamicGlobalObject()) {
95        JSLockHolder lock(exec);
96        throwError(exec, s_exception);
97    }
98
99    [s_exception release];
100    s_exception = nil;
101    s_exceptionEnvironment = 0;
102}
103
104ObjcInstance::ObjcInstance(id instance, PassRefPtr<RootObject> rootObject)
105    : Instance(rootObject)
106    , _instance(instance)
107    , _class(0)
108    , _pool(0)
109    , _beginCount(0)
110{
111}
112
113PassRefPtr<ObjcInstance> ObjcInstance::create(id instance, PassRefPtr<RootObject> rootObject)
114{
115    if (!s_instanceWrapperCache)
116        s_instanceWrapperCache = createInstanceWrapperCache();
117    if (void* existingWrapper = NSMapGet(s_instanceWrapperCache, instance))
118        return static_cast<ObjcInstance*>(existingWrapper);
119    RefPtr<ObjcInstance> wrapper = adoptRef(new ObjcInstance(instance, rootObject));
120    NSMapInsert(s_instanceWrapperCache, instance, wrapper.get());
121    return wrapper.release();
122}
123
124ObjcInstance::~ObjcInstance()
125{
126    // Both -finalizeForWebScript and -dealloc/-finalize of _instance may require autorelease pools.
127    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
128
129    ASSERT(s_instanceWrapperCache);
130    ASSERT(_instance);
131    NSMapRemove(s_instanceWrapperCache, _instance.get());
132
133    if ([_instance.get() respondsToSelector:@selector(finalizeForWebScript)])
134        [_instance.get() performSelector:@selector(finalizeForWebScript)];
135    _instance = 0;
136
137    [pool drain];
138}
139
140static NSAutoreleasePool* allocateAutoReleasePool()
141{
142    // If GC is enabled an autorelease pool is unnecessary, and the
143    // pool cannot be protected from GC so may be collected leading
144    // to a crash when we try to drain the release pool.
145    if (objc_collectingEnabled())
146        return nil;
147
148    return [[NSAutoreleasePool alloc] init];
149}
150
151void ObjcInstance::virtualBegin()
152{
153    if (!_pool)
154        _pool = allocateAutoReleasePool();
155    _beginCount++;
156}
157
158void ObjcInstance::virtualEnd()
159{
160    _beginCount--;
161    ASSERT(_beginCount >= 0);
162    if (!_beginCount) {
163        [_pool drain];
164        _pool = 0;
165    }
166}
167
168Bindings::Class* ObjcInstance::getClass() const
169{
170    if (!_instance)
171        return 0;
172    if (!_class)
173        _class = ObjcClass::classForIsA(object_getClass(_instance.get()));
174    return static_cast<Bindings::Class*>(_class);
175}
176
177bool ObjcInstance::supportsInvokeDefaultMethod() const
178{
179    return [_instance.get() respondsToSelector:@selector(invokeDefaultMethodWithArguments:)];
180}
181
182class ObjCRuntimeMethod : public RuntimeMethod {
183public:
184    static ObjCRuntimeMethod* create(ExecState* exec, JSGlobalObject* globalObject, const String& name, Bindings::Method* method)
185    {
186        // FIXME: deprecatedGetDOMStructure uses the prototype off of the wrong global object
187        // We need to pass in the right global object for "i".
188        Structure* domStructure = WebCore::deprecatedGetDOMStructure<ObjCRuntimeMethod>(exec);
189        ObjCRuntimeMethod* runtimeMethod = new (NotNull, allocateCell<ObjCRuntimeMethod>(*exec->heap())) ObjCRuntimeMethod(globalObject, domStructure, method);
190        runtimeMethod->finishCreation(exec->vm(), name);
191        return runtimeMethod;
192    }
193
194    static Structure* createStructure(VM& vm, JSC::JSGlobalObject* globalObject, JSValue prototype)
195    {
196        return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), &s_info);
197    }
198
199    static const ClassInfo s_info;
200
201private:
202    typedef RuntimeMethod Base;
203
204    ObjCRuntimeMethod(JSGlobalObject* globalObject, Structure* structure, Bindings::Method* method)
205        : RuntimeMethod(globalObject, structure, method)
206    {
207    }
208
209    void finishCreation(VM& vm, const String& name)
210    {
211        Base::finishCreation(vm, name);
212        ASSERT(inherits(&s_info));
213    }
214};
215
216const ClassInfo ObjCRuntimeMethod::s_info = { "ObjCRuntimeMethod", &RuntimeMethod::s_info, 0, 0, CREATE_METHOD_TABLE(ObjCRuntimeMethod) };
217
218JSValue ObjcInstance::getMethod(ExecState* exec, PropertyName propertyName)
219{
220    Method* method = getClass()->methodNamed(propertyName, this);
221    return ObjCRuntimeMethod::create(exec, exec->lexicalGlobalObject(), propertyName.publicName(), method);
222}
223
224JSValue ObjcInstance::invokeMethod(ExecState* exec, RuntimeMethod* runtimeMethod)
225{
226    if (!asObject(runtimeMethod)->inherits(&ObjCRuntimeMethod::s_info))
227        return throwError(exec, createTypeError(exec, "Attempt to invoke non-plug-in method on plug-in object."));
228
229    ObjcMethod *method = static_cast<ObjcMethod*>(runtimeMethod->method());
230    ASSERT(method);
231
232    return invokeObjcMethod(exec, method);
233}
234
235JSValue ObjcInstance::invokeObjcMethod(ExecState* exec, ObjcMethod* method)
236{
237    JSValue result = jsUndefined();
238
239    JSLock::DropAllLocks dropAllLocks(exec); // Can't put this inside the @try scope because it unwinds incorrectly.
240
241    setGlobalException(nil);
242
243@try {
244    NSMethodSignature* signature = method->getMethodSignature();
245    NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:signature];
246    [invocation setSelector:method->selector()];
247    [invocation setTarget:_instance.get()];
248
249    if (method->isFallbackMethod()) {
250        if (objcValueTypeForType([signature methodReturnType]) != ObjcObjectType) {
251            NSLog(@"Incorrect signature for invokeUndefinedMethodFromWebScript:withArguments: -- return type must be object.");
252            return result;
253        }
254
255        // Invoke invokeUndefinedMethodFromWebScript:withArguments:, pass JavaScript function
256        // name as first (actually at 2) argument and array of args as second.
257        NSString* jsName = (NSString* )method->javaScriptName();
258        [invocation setArgument:&jsName atIndex:2];
259
260        NSMutableArray* objcArgs = [NSMutableArray array];
261        int count = exec->argumentCount();
262        for (int i = 0; i < count; i++) {
263            ObjcValue value = convertValueToObjcValue(exec, exec->argument(i), ObjcObjectType);
264            [objcArgs addObject:value.objectValue];
265        }
266        [invocation setArgument:&objcArgs atIndex:3];
267    } else {
268        unsigned count = [signature numberOfArguments];
269        for (unsigned i = 2; i < count ; i++) {
270            const char* type = [signature getArgumentTypeAtIndex:i];
271            ObjcValueType objcValueType = objcValueTypeForType(type);
272
273            // Must have a valid argument type.  This method signature should have
274            // been filtered already to ensure that it has acceptable argument
275            // types.
276            ASSERT(objcValueType != ObjcInvalidType && objcValueType != ObjcVoidType);
277
278            ObjcValue value = convertValueToObjcValue(exec, exec->argument(i-2), objcValueType);
279
280            switch (objcValueType) {
281                case ObjcObjectType:
282                    [invocation setArgument:&value.objectValue atIndex:i];
283                    break;
284                case ObjcCharType:
285                case ObjcUnsignedCharType:
286                    [invocation setArgument:&value.charValue atIndex:i];
287                    break;
288                case ObjcShortType:
289                case ObjcUnsignedShortType:
290                    [invocation setArgument:&value.shortValue atIndex:i];
291                    break;
292                case ObjcBoolType:
293                    [invocation setArgument:&value.booleanValue atIndex:i];
294                    break;
295                case ObjcIntType:
296                case ObjcUnsignedIntType:
297                    [invocation setArgument:&value.intValue atIndex:i];
298                    break;
299                case ObjcLongType:
300                case ObjcUnsignedLongType:
301                    [invocation setArgument:&value.longValue atIndex:i];
302                    break;
303                case ObjcLongLongType:
304                case ObjcUnsignedLongLongType:
305                    [invocation setArgument:&value.longLongValue atIndex:i];
306                    break;
307                case ObjcFloatType:
308                    [invocation setArgument:&value.floatValue atIndex:i];
309                    break;
310                case ObjcDoubleType:
311                    [invocation setArgument:&value.doubleValue atIndex:i];
312                    break;
313                default:
314                    // Should never get here.  Argument types are filtered (and
315                    // the assert above should have fired in the impossible case
316                    // of an invalid type anyway).
317                    fprintf(stderr, "%s: invalid type (%d)\n", __PRETTY_FUNCTION__, (int)objcValueType);
318                    ASSERT_NOT_REACHED();
319            }
320        }
321    }
322
323    [invocation invoke];
324
325    // Get the return value type.
326    const char* type = [signature methodReturnType];
327    ObjcValueType objcValueType = objcValueTypeForType(type);
328
329    // Must have a valid return type.  This method signature should have
330    // been filtered already to ensure that it have an acceptable return
331    // type.
332    ASSERT(objcValueType != ObjcInvalidType);
333
334    // Get the return value and convert it to a JavaScript value. Length
335    // of return value will never exceed the size of largest scalar
336    // or a pointer.
337    char buffer[1024];
338    ASSERT([signature methodReturnLength] < 1024);
339
340    if (*type != 'v') {
341        [invocation getReturnValue:buffer];
342        result = convertObjcValueToValue(exec, buffer, objcValueType, m_rootObject.get());
343    }
344} @catch(NSException* localException) {
345}
346    moveGlobalExceptionToExecState(exec);
347
348    // Work around problem in some versions of GCC where result gets marked volatile and
349    // it can't handle copying from a volatile to non-volatile.
350    return const_cast<JSValue&>(result);
351}
352
353JSValue ObjcInstance::invokeDefaultMethod(ExecState* exec)
354{
355    JSValue result = jsUndefined();
356
357    JSLock::DropAllLocks dropAllLocks(exec); // Can't put this inside the @try scope because it unwinds incorrectly.
358    setGlobalException(nil);
359
360@try {
361    if (![_instance.get() respondsToSelector:@selector(invokeDefaultMethodWithArguments:)])
362        return result;
363
364    NSMethodSignature* signature = [_instance.get() methodSignatureForSelector:@selector(invokeDefaultMethodWithArguments:)];
365    NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:signature];
366    [invocation setSelector:@selector(invokeDefaultMethodWithArguments:)];
367    [invocation setTarget:_instance.get()];
368
369    if (objcValueTypeForType([signature methodReturnType]) != ObjcObjectType) {
370        NSLog(@"Incorrect signature for invokeDefaultMethodWithArguments: -- return type must be object.");
371        return result;
372    }
373
374    NSMutableArray* objcArgs = [NSMutableArray array];
375    unsigned count = exec->argumentCount();
376    for (unsigned i = 0; i < count; i++) {
377        ObjcValue value = convertValueToObjcValue(exec, exec->argument(i), ObjcObjectType);
378        [objcArgs addObject:value.objectValue];
379    }
380    [invocation setArgument:&objcArgs atIndex:2];
381
382    [invocation invoke];
383
384    // Get the return value type, should always be "@" because of
385    // check above.
386    const char* type = [signature methodReturnType];
387    ObjcValueType objcValueType = objcValueTypeForType(type);
388
389    // Get the return value and convert it to a JavaScript value. Length
390    // of return value will never exceed the size of a pointer, so we're
391    // OK with 32 here.
392    char buffer[32];
393    [invocation getReturnValue:buffer];
394    result = convertObjcValueToValue(exec, buffer, objcValueType, m_rootObject.get());
395} @catch(NSException* localException) {
396}
397    moveGlobalExceptionToExecState(exec);
398
399    // Work around problem in some versions of GCC where result gets marked volatile and
400    // it can't handle copying from a volatile to non-volatile.
401    return const_cast<JSValue&>(result);
402}
403
404bool ObjcInstance::setValueOfUndefinedField(ExecState* exec, PropertyName propertyName, JSValue aValue)
405{
406    String name(propertyName.publicName());
407    if (name.isNull())
408        return false;
409
410    id targetObject = getObject();
411    if (![targetObject respondsToSelector:@selector(setValue:forUndefinedKey:)])
412        return false;
413
414    JSLock::DropAllLocks dropAllLocks(exec); // Can't put this inside the @try scope because it unwinds incorrectly.
415
416    // This check is not really necessary because NSObject implements
417    // setValue:forUndefinedKey:, and unfortunately the default implementation
418    // throws an exception.
419    if ([targetObject respondsToSelector:@selector(setValue:forUndefinedKey:)]){
420        setGlobalException(nil);
421
422        ObjcValue objcValue = convertValueToObjcValue(exec, aValue, ObjcObjectType);
423
424        @try {
425            [targetObject setValue:objcValue.objectValue forUndefinedKey:[NSString stringWithCString:name.ascii().data() encoding:NSASCIIStringEncoding]];
426        } @catch(NSException* localException) {
427            // Do nothing.  Class did not override valueForUndefinedKey:.
428        }
429
430        moveGlobalExceptionToExecState(exec);
431    }
432
433    return true;
434}
435
436JSValue ObjcInstance::getValueOfUndefinedField(ExecState* exec, PropertyName propertyName) const
437{
438    String name(propertyName.publicName());
439    if (name.isNull())
440        return jsUndefined();
441
442    JSValue result = jsUndefined();
443
444    id targetObject = getObject();
445
446    JSLock::DropAllLocks dropAllLocks(exec); // Can't put this inside the @try scope because it unwinds incorrectly.
447
448    // This check is not really necessary because NSObject implements
449    // valueForUndefinedKey:, and unfortunately the default implementation
450    // throws an exception.
451    if ([targetObject respondsToSelector:@selector(valueForUndefinedKey:)]){
452        setGlobalException(nil);
453
454        @try {
455            id objcValue = [targetObject valueForUndefinedKey:[NSString stringWithCString:name.ascii().data() encoding:NSASCIIStringEncoding]];
456            result = convertObjcValueToValue(exec, &objcValue, ObjcObjectType, m_rootObject.get());
457        } @catch(NSException* localException) {
458            // Do nothing.  Class did not override valueForUndefinedKey:.
459        }
460
461        moveGlobalExceptionToExecState(exec);
462    }
463
464    // Work around problem in some versions of GCC where result gets marked volatile and
465    // it can't handle copying from a volatile to non-volatile.
466    return const_cast<JSValue&>(result);
467}
468
469JSValue ObjcInstance::defaultValue(ExecState* exec, PreferredPrimitiveType hint) const
470{
471    if (hint == PreferString)
472        return stringValue(exec);
473    if (hint == PreferNumber)
474        return numberValue(exec);
475    if ([_instance.get() isKindOfClass:[NSString class]])
476        return stringValue(exec);
477    if ([_instance.get() isKindOfClass:[NSNumber class]])
478        return numberValue(exec);
479    return valueOf(exec);
480}
481
482JSValue ObjcInstance::stringValue(ExecState* exec) const
483{
484    return convertNSStringToString(exec, [getObject() description]);
485}
486
487JSValue ObjcInstance::numberValue(ExecState*) const
488{
489    // FIXME:  Implement something sensible
490    return jsNumber(0);
491}
492
493JSValue ObjcInstance::booleanValue() const
494{
495    // FIXME:  Implement something sensible
496    return jsBoolean(false);
497}
498
499JSValue ObjcInstance::valueOf(ExecState* exec) const
500{
501    return stringValue(exec);
502}
503