/** Implementation of NSInvocation for GNUStep Copyright (C) 1998,2003 Free Software Foundation, Inc. Author: Richard Frith-Macdonald Date: August 1998 Based on code by: Andrew Kachites McCallum This file is part of the GNUstep Base Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111 USA. NSInvocation class reference $Date$ $Revision$ */ #import "config.h" #define EXPOSE_NSInvocation_IVARS 1 #import "Foundation/NSException.h" #import "Foundation/NSCoder.h" #import "Foundation/NSInvocation.h" #import "GSInvocation.h" #import "GSPrivate.h" #import "GNUstepBase/NSObject+GNUstepBase.h" #if defined(USE_LIBFFI) #include "cifframe.h" #elif defined(USE_FFCALL) #include "callframe.h" #endif #if defined(HAVE_SYS_MMAN_H) #include #endif #if defined(HAVE_MMAP) # if !defined(MAP_ANONYMOUS) # if defined(MAP_ANON) # define MAP_ANONYMOUS MAP_ANON # else # undef HAVE_MMAP # endif # endif #endif @implementation GSCodeBuffer + (GSCodeBuffer*) memoryWithSize: (NSUInteger)_size { return [[[self alloc] initWithSize: _size] autorelease]; } - (void*) buffer { return buffer; } - (void) dealloc { if (size > 0) { #if defined(HAVE_MMAP) munmap(buffer, size); #elif defined(__MINGW32__) VirtualFree(buffer, 0, MEM_RELEASE); #else free(buffer); #endif } [super dealloc]; } - (id) initWithSize: (NSUInteger)_size { #if defined(HAVE_MMAP) #if defined(HAVE_MPROTECT) /* We have mprotect, so we create memory as writable and change it to * executable later (writable and executable may not be possible at * the same time). */ buffer = mmap (NULL, _size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); #else /* We do not have mprotect, so we have to try to create writable and * executable memory. */ buffer = mmap (NULL, _size, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); #endif /* HAVE_MPROTECT */ if (buffer == (void*)-1) buffer = (void*)0; #elif defined(__MINGW32__) buffer = VirtualAlloc(NULL, _size, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); #else buffer = malloc(_size); #endif /* HAVE_MMAP */ if (buffer == (void*)0) { NSLog(@"Failed to map %u bytes for execute: %@", _size, [NSError _last]); buffer = 0; [self dealloc]; self = nil; } else { size = _size; } return self; } /* Ensure that the protection on the buffer is such that it will execute * on any architecture. */ - (void) protect { #if defined(__MINGW32__) DWORD old; if (VirtualProtect(buffer, size, PAGE_EXECUTE, &old) == 0) { NSLog(@"Failed to protect memory as executable: %@", [NSError _last]); } #elif defined(HAVE_MPROTECT) if (mprotect(buffer, size, PROT_READ|PROT_EXEC) == -1) { NSLog(@"Failed to protect memory as executable: %@", [NSError _last]); } #endif } @end static Class NSInvocation_abstract_class; static Class NSInvocation_concrete_class; @interface GSInvocationProxy { @public Class isa; id target; NSInvocation *invocation; } + (id) _newWithTarget: (id)t; - (NSInvocation*) _invocation; - (void) forwardInvocation: (NSInvocation*)anInvocation; - (NSMethodSignature*) methodSignatureForSelector: (SEL)aSelector; @end @interface GSMessageProxy : GSInvocationProxy @end #define _inf ((NSArgumentInfo*)_info) /** *

The NSInvocation class implements a mechanism of constructing * messages (as NSInvocation instances), sending these to other * objects, and handling the returned values. *

*

An NSInvocation object may contain a target object to which a * message can be sent, or may send the message to an arbitrary object.
* Each message consists of a selector for that method and an argument * list. Once the message has been sent, the invocation will contain * a return value whose contents may be copied out of it. *

*

The target, selector, and arguments of an instance be constructed * dynamically, providing a great deal of power/flexibility. *

*

The sending of the message to the target object (using the -invoke * or -invokeWithTarget: method) can be done at any time, but a standard * use of this is by the [NSObject-forwardInvocation:] method which is * called whenever a method is not implemented by the class of the * object to which it was sent. *

*

Related to the class are two convenience macros ... NS_MESSAGE() * and NS_INVOCATION() ... to allow easy construction of invocations * with all the arguments set up. *

*/ @implementation NSInvocation #ifdef USE_LIBFFI static inline void _get_arg(NSInvocation *inv, int index, void *buffer) { cifframe_get_arg((cifframe_t *)inv->_cframe, index, buffer, ((NSArgumentInfo*)inv->_info)[index+1].size); } static inline void _set_arg(NSInvocation *inv, int index, void *buffer) { cifframe_set_arg((cifframe_t *)inv->_cframe, index, buffer, ((NSArgumentInfo*)inv->_info)[index+1].size); } static inline void * _arg_addr(NSInvocation *inv, int index) { return cifframe_arg_addr((cifframe_t *)inv->_cframe, index); } #elif defined(USE_FFCALL) static inline void _get_arg(NSInvocation *inv, int index, void *buffer) { callframe_get_arg((callframe_t *)inv->_cframe, index, buffer, ((NSArgumentInfo*)inv->_info)[index+1].size); } static inline void _set_arg(NSInvocation *inv, int index, void *buffer) { callframe_set_arg((callframe_t *)inv->_cframe, index, buffer, ((NSArgumentInfo*)inv->_info)[index+1].size); } static inline void * _arg_addr(NSInvocation *inv, int index) { return callframe_arg_addr((callframe_t *)inv->_cframe, index); } #else static inline void _get_arg(NSInvocation *inv, int index, void *buffer) { } static inline void _set_arg(NSInvocation *inv, int index, void *buffer) { } static inline void * _arg_addr(NSInvocation *inv, int index) { return 0; } #endif + (id) allocWithZone: (NSZone*)aZone { if (self == NSInvocation_abstract_class) { return NSAllocateObject(NSInvocation_concrete_class, 0, aZone); } else { return NSAllocateObject(self, 0, aZone); } } + (void) initialize { if (self == [NSInvocation class]) { NSInvocation_abstract_class = self; #if defined(USE_LIBFFI) NSInvocation_concrete_class = [GSFFIInvocation class]; #elif defined(USE_FFCALL) NSInvocation_concrete_class = [GSFFCallInvocation class]; #else NSInvocation_concrete_class = [GSDummyInvocation class]; #endif } } /** * Returns an invocation instance which can be used to send messages to * a target object using the described signature.
* You must set the target and selector (using -setTarget: and -setSelector:) * before you attempt to use the invocation.
* Raises an NSInvalidArgumentException if the signature is nil. */ + (NSInvocation*) invocationWithMethodSignature: (NSMethodSignature*)_signature { return AUTORELEASE([[NSInvocation_concrete_class alloc] initWithMethodSignature: _signature]); } - (void) dealloc { if (_targetRetained) { _targetRetained = NO; RELEASE(_target); } if (_argsRetained) { _argsRetained = NO; if (_cframe && _sig) { unsigned int i; for (i = 3; i <= _numArgs; i++) { if (*_inf[i].type == _C_CHARPTR) { char *str = 0; _get_arg(self, i-1, &str); if (str != 0) { NSZoneFree(NSDefaultMallocZone(), str); } } else if (*_inf[i].type == _C_ID) { id obj = nil; _get_arg(self, i-1, &obj); RELEASE(obj); } } } } CLEAR_RETURN_VALUE_IF_OBJECT; #if defined(USE_LIBFFI) if (_cframe) { NSZoneFree(NSDefaultMallocZone(), _cframe); } #elif defined(USE_FFCALL) if (_cframe) { NSZoneFree(NSDefaultMallocZone(), _cframe); } #endif if (_retptr) { NSZoneFree(NSDefaultMallocZone(), _retptr); } RELEASE(_sig); [super dealloc]; } /** * Copies the argument identified by index into the memory location specified * by the buffer argument.
* An index of zero is the target object, an index of one is the selector, * so the actual method arguments start at index 2. */ - (void) getArgument: (void*)buffer atIndex: (NSInteger)index { if ((NSUInteger)index >= _numArgs) { [NSException raise: NSInvalidArgumentException format: @"bad invocation argument index"]; } if (index == 0) { *(id*)buffer = _target; } else if (index == 1) { *(SEL*)buffer = _selector; } else { _get_arg(self, index, buffer); } } /** * Copies the invocations return value to the location pointed to by buffer * if a return value has been set (see the -setReturnValue: method).
* If there isn't a return value then this method raises an exception. */ - (void) getReturnValue: (void*)buffer { const char *type; if (_validReturn == NO) { [NSException raise: NSGenericException format: @"getReturnValue with no value set"]; } type = [_sig methodReturnType]; if (*_inf[0].type != _C_VOID) { memcpy(buffer, _retval, _inf[0].size); } } /** * Returns the selector of the invocation (the argument at index 1) */ - (SEL) selector { return _selector; } /** * Sets the argument identified by index from the memory location specified * by the buffer argument.
* Using an index of 0 is equivalent to calling -setTarget: and using an * argument of 1 is equivalent to -setSelector:
* Proper arguments start at index 2.
* NB. Unlike -setTarget: and -setSelector: the value of buffer must be * a pointer to the argument to be set in the invocation.
* If -retainArguments was called, then any object argument set in the * receiver is retained by it. */ - (void) setArgument: (void*)buffer atIndex: (NSInteger)index { if ((NSUInteger)index >= _numArgs) { [NSException raise: NSInvalidArgumentException format: @"bad invocation argument index"]; } if (index == 0) { [self setTarget: *(id*)buffer]; } else if (index == 1) { [self setSelector: *(SEL*)buffer]; } else { int i = index+1; /* Allow for return type in '_inf' */ const char *type = _inf[i].type; if (_argsRetained && (*type == _C_ID || *type == _C_CHARPTR)) { if (*type == _C_ID) { id old; _get_arg(self, index, &old); _set_arg(self, index, buffer); IF_NO_GC(RETAIN(*(id*)buffer)); if (old != nil) { RELEASE(old); } } else { char *oldstr; char *newstr = *(char**)buffer; _get_arg(self, index, &oldstr); if (newstr == 0) { _set_arg(self, index, buffer); } else { char *tmp; tmp = NSZoneMalloc(NSDefaultMallocZone(), strlen(newstr)+1); strcpy(tmp, newstr); _set_arg(self, index, tmp); } if (oldstr != 0) { NSZoneFree(NSDefaultMallocZone(), oldstr); } } } else { _set_arg(self, index, buffer); } } } /** * Sets the return value of the invocation to the item that buffer points to. */ - (void) setReturnValue: (void*)buffer { const char *type; type = _inf[0].type; CLEAR_RETURN_VALUE_IF_OBJECT; if (*type != _C_VOID) { memcpy(_retval, buffer, _inf[0].size); } RETAIN_RETURN_VALUE; _validReturn = YES; } /** * Sets the selector for the invocation. */ - (void) setSelector: (SEL)aSelector { _selector = aSelector; } /** * Sets the target object for the invocation.
* If -retainArguments was called, then the target is retained. */ - (void) setTarget: (id)anObject { if (_targetRetained) { ASSIGN(_target, anObject); } else { _target = anObject; } } /** * Returns the target object of the invocation. */ - (id) target { return _target; } /** * Returns a flag to indicate whether object arguments of the invocation * (including its target) are retained by the invocation. */ - (BOOL) argumentsRetained { return _argsRetained; } /** * Instructs the invocation to retain its object arguments (including the * target). The default is not to retain them. */ - (void) retainArguments { [self retainArgumentsIncludingTarget: YES]; } /** * Returns YES if target has been retained yet, NO otherwise. */ - (BOOL) targetRetained { return _targetRetained; } /** * Similar to -[NSInvocation retainArguments], but allows the sender to * explicitly control whether the target is retained as well. Retaining * the target is sometimes not desirable (such as in NSUndoManager), as * retain loops could result. */ - (void) retainArgumentsIncludingTarget: (BOOL)retainTargetFlag { if (_argsRetained == NO) { unsigned int i; _argsRetained = YES; if (_cframe == 0) { return; } for (i = 3; i <= _numArgs; i++) { if (*_inf[i].type == _C_ID) { id old; _get_arg(self, i-1, &old); if (old != nil) { IF_NO_GC(RETAIN(old)); } } else if (*_inf[i].type == _C_CHARPTR) { char *str; _get_arg(self, i-1, &str); if (str != 0) { char *tmp; tmp = NSZoneMalloc(NSDefaultMallocZone(), strlen(str)+1); strcpy(tmp, str); _set_arg(self, i-1, &tmp); } } } } if (retainTargetFlag && _targetRetained == NO) { _targetRetained = YES; IF_NO_GC(RETAIN(_target)); } } /** * Sends the message encapsulated in the invocation to its target. */ - (void) invoke { [self invokeWithTarget: _target]; } /** * Sends the message encapsulated in the invocation to anObject. */ - (void) invokeWithTarget: (id)anObject { [self subclassResponsibility: _cmd]; } /** * Returns the method signature of the invocation. */ - (NSMethodSignature*) methodSignature { return _sig; } - (NSString*) description { /* * Don't use -[NSString stringWithFormat:] method because it can cause * infinite recursion. */ char buffer[1024]; snprintf (buffer, 1024, "<%s %p selector: %s target: %s>", \ GSClassNameFromObject(self), \ self, \ _selector ? GSNameFromSelector(_selector) : "nil", \ _target ? GSNameFromClass([_target class]) : "nil" \ ); return [NSString stringWithUTF8String: buffer]; } - (void) encodeWithCoder: (NSCoder*)aCoder { const char *types = [_sig methodType]; unsigned int i; [aCoder encodeValueOfObjCType: @encode(char*) at: &types]; [aCoder encodeObject: _target]; [aCoder encodeValueOfObjCType: _inf[2].type at: &_selector]; for (i = 3; i <= _numArgs; i++) { const char *type = _inf[i].type; void *datum; datum = _arg_addr(self, i-1); if (*type == _C_ID) { [aCoder encodeObject: *(id*)datum]; } else { [aCoder encodeValueOfObjCType: type at: datum]; } } if (*_inf[0].type != _C_VOID) { [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_validReturn]; if (_validReturn) { [aCoder encodeValueOfObjCType: _inf[0].type at: _retval]; } } } - (id) initWithCoder: (NSCoder*)aCoder { NSMethodSignature *newSig; const char *types; void *datum; unsigned int i; [aCoder decodeValueOfObjCType: @encode(char*) at: &types]; newSig = [NSMethodSignature signatureWithObjCTypes: types]; NSZoneFree(NSDefaultMallocZone(), (void*)types); RELEASE(self); self = RETAIN([NSInvocation invocationWithMethodSignature: newSig]); [aCoder decodeValueOfObjCType: @encode(id) at: &_target]; [aCoder decodeValueOfObjCType: @encode(SEL) at: &_selector]; for (i = 3; i <= _numArgs; i++) { datum = _arg_addr(self, i-1); [aCoder decodeValueOfObjCType: _inf[i].type at: datum]; } _argsRetained = YES; if (*_inf[0].type != _C_VOID) { [aCoder decodeValueOfObjCType: @encode(BOOL) at: &_validReturn]; if (_validReturn) { [aCoder decodeValueOfObjCType: _inf[0].type at: _retval]; } } return self; } @end /** * Provides some minor extensions and some utility methods to aid * integration of NSInvocation with the Objective-C runtime. */ @implementation NSInvocation (GNUstep) - (BOOL) sendsToSuper { return _sendToSuper; } - (void) setSendsToSuper: (BOOL)flag { _sendToSuper = flag; } @end /** * These methods are for internal use only ... not public API
* They are used by the NS_INVOCATION() and NS_MESSAGE() macros to help * create invocations. */ @implementation NSInvocation (MacroSetup) /** * Internal use.
* Initialises the receiver with a known selector and argument list * as supplied to the forward:: method by the ObjectiveC runtime * when it is unable to locate an implementation for the selector * in a class. */ - (id) initWithArgframe: (arglist_t)frame selector: (SEL)aSelector { [self subclassResponsibility: _cmd]; return nil; } /** * Initialised an invocation instance which can be used to send messages to * a target object using aSignature.
* You must set the target and selector (using -setTarget: and -setSelector:) * before you attempt to use the invocation.
* Raises an NSInvalidArgumentException if aSignature is nil. */ - (id) initWithMethodSignature: (NSMethodSignature*)aSignature { [self subclassResponsibility: _cmd]; return nil; } /** * Tries to produce a method signature based on aSelector and uses that to * initialise self by calling the -initWithMethodSignature: method.
* If the argument type of aSelector cannot be determined, this releases self * and returns nil. */ - (id) initWithSelector: (SEL)aSelector { const char *types; NSMethodSignature *newSig; types = sel_get_type(aSelector); if (types == 0) { types = sel_get_type(sel_get_any_typed_uid(GSNameFromSelector(aSelector))); } if (types == 0) { NSLog(@"Couldn't find encoding type for selector %s.", GSNameFromSelector(aSelector)); RELEASE(self); return nil; } newSig = [NSMethodSignature signatureWithObjCTypes: types]; return [self initWithMethodSignature: newSig]; } /** * Internal use.
* Provides a return frame that the ObjectiveC runtime can use to * return the result of an invocation to a calling function. */ - (void*) returnFrame: (arglist_t)argFrame { [self subclassResponsibility: _cmd]; return NULL; } + (id) _newProxyForInvocation: (id)target { return [GSInvocationProxy _newWithTarget: target]; } + (id) _newProxyForMessage: (id)target { return [GSMessageProxy _newWithTarget: target]; } + (NSInvocation*) _returnInvocationAndDestroyProxy: (id)proxy { NSInvocation *inv = [proxy _invocation]; NSDeallocateObject(proxy); return inv; } @end @implementation NSInvocation (BackwardCompatibility) - (void) invokeWithObject: (id)obj { [self invokeWithTarget: (id)obj]; } @end #if !defined(USE_FFCALL) && !defined(USE_LIBFFI) #warning Using dummy NSInvocation implementation. It is strongly recommended that you use libffi. @implementation GSDummyInvocation - (id) initWithArgframe: (arglist_t)frame selector: (SEL)aSelector { return self; } /* * This is the de_signated initialiser. */ - (id) initWithMethodSignature: (NSMethodSignature*)aSignature { return self; } - (void) invokeWithTarget: (id)anObject { CLEAR_RETURN_VALUE_IF_OBJECT; _validReturn = NO; /* * A message to a nil object returns nil. */ if (anObject == nil) { _validReturn = YES; return; } } - (void*) returnFrame: (arglist_t)argFrame { return 0; } @end #endif @implementation GSInvocationProxy + (id) _newWithTarget: (id)t { GSInvocationProxy *o; o = (GSInvocationProxy*) NSAllocateObject(self, 0, NSDefaultMallocZone()); o->target = RETAIN(t); return o; } - (NSInvocation*) _invocation { return invocation; } - (retval_t) forward: (SEL)aSel : (arglist_t)argFrame { NSInvocation *inv; if (aSel == 0) [NSException raise: NSInvalidArgumentException format: @"%@ null selector given", NSStringFromSelector(_cmd)]; inv = AUTORELEASE([[NSInvocation alloc] initWithArgframe: argFrame selector: aSel]); [self forwardInvocation: inv]; return [inv returnFrame: argFrame]; } - (void) forwardInvocation: (NSInvocation*)anInvocation { invocation = anInvocation; } - (NSMethodSignature*) methodSignatureForSelector: (SEL)aSelector { return [target methodSignatureForSelector: aSelector]; } @end @implementation GSMessageProxy - (NSInvocation*) _invocation { [invocation setTarget: target]; return invocation; } @end