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