2 * Copyright (C) 2004, 2008, 2009 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #import "objc_instance.h"
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>
41 #define OBJC_LOG(formatAndArgs...) ((void)0)
43 #define OBJC_LOG(formatAndArgs...) { \
44 fprintf (stderr, "%s:%d -- %s: ", __FILE__, __LINE__, __FUNCTION__); \
45 fprintf(stderr, formatAndArgs); \
49 using namespace JSC::Bindings;
52 static NSString *s_exception;
53 static JSGlobalObject* s_exceptionEnvironment; // No need to protect this value, since we just use it for a pointer comparison.
54 static NSMapTable *s_instanceWrapperCache;
57 #pragma clang diagnostic push
58 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
61 static NSMapTable *createInstanceWrapperCache()
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];
70 #pragma clang diagnostic pop
73 RuntimeObject* ObjcInstance::newRuntimeObject(ExecState* exec)
75 return ObjCRuntimeObject::create(exec, exec->lexicalGlobalObject(), this);
78 void ObjcInstance::setGlobalException(NSString* exception, JSGlobalObject* exceptionEnvironment)
80 NSString *oldException = s_exception;
81 s_exception = [exception copy];
82 [oldException release];
84 s_exceptionEnvironment = exceptionEnvironment;
87 void ObjcInstance::moveGlobalExceptionToExecState(ExecState* exec)
90 ASSERT(!s_exceptionEnvironment);
94 if (!s_exceptionEnvironment || s_exceptionEnvironment == exec->dynamicGlobalObject()) {
95 JSLock lock(SilenceAssertionsOnly);
96 throwError(exec, s_exception);
99 [s_exception release];
101 s_exceptionEnvironment = 0;
104 ObjcInstance::ObjcInstance(id instance, PassRefPtr<RootObject> rootObject)
105 : Instance(rootObject)
106 , _instance(instance)
113 PassRefPtr<ObjcInstance> ObjcInstance::create(id instance, PassRefPtr<RootObject> rootObject)
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();
124 ObjcInstance::~ObjcInstance()
126 // Both -finalizeForWebScript and -dealloc/-finalize of _instance may require autorelease pools.
127 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
129 ASSERT(s_instanceWrapperCache);
131 NSMapRemove(s_instanceWrapperCache, _instance.get());
133 if ([_instance.get() respondsToSelector:@selector(finalizeForWebScript)])
134 [_instance.get() performSelector:@selector(finalizeForWebScript)];
140 static NSAutoreleasePool* allocateAutoReleasePool()
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())
148 return [[NSAutoreleasePool alloc] init];
151 void ObjcInstance::virtualBegin()
154 _pool = allocateAutoReleasePool();
158 void ObjcInstance::virtualEnd()
161 ASSERT(_beginCount >= 0);
168 Bindings::Class* ObjcInstance::getClass() const
173 _class = ObjcClass::classForIsA(object_getClass(_instance.get()));
174 return static_cast<Bindings::Class*>(_class);
177 bool ObjcInstance::supportsInvokeDefaultMethod() const
179 return [_instance.get() respondsToSelector:@selector(invokeDefaultMethodWithArguments:)];
182 class ObjCRuntimeMethod : public RuntimeMethod {
184 static ObjCRuntimeMethod* create(ExecState* exec, JSGlobalObject* globalObject, const UString& name, Bindings::MethodList& list)
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* method = new (NotNull, allocateCell<ObjCRuntimeMethod>(*exec->heap())) ObjCRuntimeMethod(globalObject, domStructure, list);
190 method->finishCreation(exec->globalData(), name);
194 static Structure* createStructure(JSGlobalData& globalData, JSC::JSGlobalObject* globalObject, JSValue prototype)
196 return Structure::create(globalData, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), &s_info);
199 static const ClassInfo s_info;
202 typedef RuntimeMethod Base;
204 ObjCRuntimeMethod(JSGlobalObject* globalObject, Structure* structure, Bindings::MethodList& list)
205 : RuntimeMethod(globalObject, structure, list)
209 void finishCreation(JSGlobalData& globalData, const UString& name)
211 Base::finishCreation(globalData, name);
212 ASSERT(inherits(&s_info));
216 const ClassInfo ObjCRuntimeMethod::s_info = { "ObjCRuntimeMethod", &RuntimeMethod::s_info, 0, 0, CREATE_METHOD_TABLE(ObjCRuntimeMethod) };
218 JSValue ObjcInstance::getMethod(ExecState* exec, PropertyName propertyName)
220 MethodList methodList = getClass()->methodsNamed(propertyName, this);
221 return ObjCRuntimeMethod::create(exec, exec->lexicalGlobalObject(), propertyName.publicName(), methodList);
224 JSValue ObjcInstance::invokeMethod(ExecState* exec, RuntimeMethod* runtimeMethod)
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."));
229 const MethodList& methodList = *runtimeMethod->methods();
231 // Overloading methods is not allowed in ObjectiveC. Should only be one
232 // name match for a particular method.
233 ASSERT(methodList.size() == 1);
235 return invokeObjcMethod(exec, static_cast<ObjcMethod*>(methodList[0]));
238 JSValue ObjcInstance::invokeObjcMethod(ExecState* exec, ObjcMethod* method)
240 JSValue result = jsUndefined();
242 JSLock::DropAllLocks dropAllLocks(SilenceAssertionsOnly); // Can't put this inside the @try scope because it unwinds incorrectly.
244 setGlobalException(nil);
247 NSMethodSignature* signature = method->getMethodSignature();
248 NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:signature];
249 [invocation setSelector:method->selector()];
250 [invocation setTarget:_instance.get()];
252 if (method->isFallbackMethod()) {
253 if (objcValueTypeForType([signature methodReturnType]) != ObjcObjectType) {
254 NSLog(@"Incorrect signature for invokeUndefinedMethodFromWebScript:withArguments: -- return type must be object.");
258 // Invoke invokeUndefinedMethodFromWebScript:withArguments:, pass JavaScript function
259 // name as first (actually at 2) argument and array of args as second.
260 NSString* jsName = (NSString* )method->javaScriptName();
261 [invocation setArgument:&jsName atIndex:2];
263 NSMutableArray* objcArgs = [NSMutableArray array];
264 int count = exec->argumentCount();
265 for (int i = 0; i < count; i++) {
266 ObjcValue value = convertValueToObjcValue(exec, exec->argument(i), ObjcObjectType);
267 [objcArgs addObject:value.objectValue];
269 [invocation setArgument:&objcArgs atIndex:3];
271 unsigned count = [signature numberOfArguments];
272 for (unsigned i = 2; i < count ; i++) {
273 const char* type = [signature getArgumentTypeAtIndex:i];
274 ObjcValueType objcValueType = objcValueTypeForType(type);
276 // Must have a valid argument type. This method signature should have
277 // been filtered already to ensure that it has acceptable argument
279 ASSERT(objcValueType != ObjcInvalidType && objcValueType != ObjcVoidType);
281 ObjcValue value = convertValueToObjcValue(exec, exec->argument(i-2), objcValueType);
283 switch (objcValueType) {
285 [invocation setArgument:&value.objectValue atIndex:i];
288 case ObjcUnsignedCharType:
289 [invocation setArgument:&value.charValue atIndex:i];
292 case ObjcUnsignedShortType:
293 [invocation setArgument:&value.shortValue atIndex:i];
296 case ObjcUnsignedIntType:
297 [invocation setArgument:&value.intValue atIndex:i];
300 case ObjcUnsignedLongType:
301 [invocation setArgument:&value.longValue atIndex:i];
303 case ObjcLongLongType:
304 case ObjcUnsignedLongLongType:
305 [invocation setArgument:&value.longLongValue atIndex:i];
308 [invocation setArgument:&value.floatValue atIndex:i];
311 [invocation setArgument:&value.doubleValue atIndex:i];
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();
325 // Get the return value type.
326 const char* type = [signature methodReturnType];
327 ObjcValueType objcValueType = objcValueTypeForType(type);
329 // Must have a valid return type. This method signature should have
330 // been filtered already to ensure that it have an acceptable return
332 ASSERT(objcValueType != ObjcInvalidType);
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
338 ASSERT([signature methodReturnLength] < 1024);
341 [invocation getReturnValue:buffer];
342 result = convertObjcValueToValue(exec, buffer, objcValueType, m_rootObject.get());
344 } @catch(NSException* localException) {
346 moveGlobalExceptionToExecState(exec);
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);
353 JSValue ObjcInstance::invokeDefaultMethod(ExecState* exec)
355 JSValue result = jsUndefined();
357 JSLock::DropAllLocks dropAllLocks(SilenceAssertionsOnly); // Can't put this inside the @try scope because it unwinds incorrectly.
358 setGlobalException(nil);
361 if (![_instance.get() respondsToSelector:@selector(invokeDefaultMethodWithArguments:)])
364 NSMethodSignature* signature = [_instance.get() methodSignatureForSelector:@selector(invokeDefaultMethodWithArguments:)];
365 NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:signature];
366 [invocation setSelector:@selector(invokeDefaultMethodWithArguments:)];
367 [invocation setTarget:_instance.get()];
369 if (objcValueTypeForType([signature methodReturnType]) != ObjcObjectType) {
370 NSLog(@"Incorrect signature for invokeDefaultMethodWithArguments: -- return type must be object.");
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];
380 [invocation setArgument:&objcArgs atIndex:2];
384 // Get the return value type, should always be "@" because of
386 const char* type = [signature methodReturnType];
387 ObjcValueType objcValueType = objcValueTypeForType(type);
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
393 [invocation getReturnValue:buffer];
394 result = convertObjcValueToValue(exec, buffer, objcValueType, m_rootObject.get());
395 } @catch(NSException* localException) {
397 moveGlobalExceptionToExecState(exec);
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);
404 bool ObjcInstance::setValueOfUndefinedField(ExecState* exec, PropertyName propertyName, JSValue aValue)
406 UString name(propertyName.publicName());
410 id targetObject = getObject();
411 if (![targetObject respondsToSelector:@selector(setValue:forUndefinedKey:)])
414 JSLock::DropAllLocks dropAllLocks(SilenceAssertionsOnly); // Can't put this inside the @try scope because it unwinds incorrectly.
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);
422 ObjcValue objcValue = convertValueToObjcValue(exec, aValue, ObjcObjectType);
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:.
430 moveGlobalExceptionToExecState(exec);
436 JSValue ObjcInstance::getValueOfUndefinedField(ExecState* exec, PropertyName propertyName) const
438 UString name(propertyName.publicName());
440 return jsUndefined();
442 JSValue result = jsUndefined();
444 id targetObject = getObject();
446 JSLock::DropAllLocks dropAllLocks(SilenceAssertionsOnly); // Can't put this inside the @try scope because it unwinds incorrectly.
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);
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:.
461 moveGlobalExceptionToExecState(exec);
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);
469 JSValue ObjcInstance::defaultValue(ExecState* exec, PreferredPrimitiveType hint) const
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);
482 JSValue ObjcInstance::stringValue(ExecState* exec) const
484 return convertNSStringToString(exec, [getObject() description]);
487 JSValue ObjcInstance::numberValue(ExecState*) const
489 // FIXME: Implement something sensible
493 JSValue ObjcInstance::booleanValue() const
495 // FIXME: Implement something sensible
496 return jsBoolean(false);
499 JSValue ObjcInstance::valueOf(ExecState* exec) const
501 return stringValue(exec);