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