diff --git a/Source/NSProtocolChecker.m b/Source/NSProtocolChecker.m index de215e9d5..8639acece 100644 --- a/Source/NSProtocolChecker.m +++ b/Source/NSProtocolChecker.m @@ -1,9 +1,9 @@ /** Implementation of NSProtocolChecker for GNUStep Copyright (C) 1995 Free Software Foundation, Inc. - Written by: Mike Kienenberger + Original by: Mike Kienenberger Date: Jun 1998 - Rewrite: Richard Frith-Macdonald + Written: Richard Frith-Macdonald Date: April 2004 This file is part of the GNUstep Base Library. @@ -36,11 +36,9 @@ @implementation NSProtocolChecker /** - * Allocates and initializes an NSProtocolChecker instance that will - * forward any messages in the aProtocol protocol to anObject, its - * target. Thus, the checker can be vended in lieu of anObject to - * restrict the messages that can be sent to anObject. Returns the - * new instance. + * Allocates and initializes an NSProtocolChecker instance by calling + * -initWithTarget:protocol:
+ * Autoreleases and returns the new instance. */ + (id) protocolCheckerWithTarget: (NSObject*)anObject protocol: (Protocol*)aProtocol @@ -55,24 +53,65 @@ [super dealloc]; } +- (struct objc_method_description*) _methodDescription: (SEL)aSelector +{ + extern struct objc_method_description *GSDescriptionForInstanceMethod(); + extern struct objc_method_description *GSDescriptionForClassMethod(); + + if (_myProtocol != nil && _myTarget != nil) + { + struct objc_method_description* mth; + + /* Older gcc versions may not initialise Protocol objects properly + * so we have an evil hack which checks for a known bad value of + * the class pointer, and uses an internal function + * (implemented in NSObject.m) to examine the protocol contents + * without sending any ObjectiveC message to it. + */ + if (GSObjCIsInstance(_myTarget)) + { + if ((int)GSObjCClass(_myProtocol) == 0x2) + { + mth = GSDescriptionForInstanceMethod(_myProtocol, aSelector); + } + else + { + mth = [_myProtocol descriptionForInstanceMethod: aSelector]; + } + } + else + { + if ((int)GSObjCClass(_myProtocol) == 0x2) + { + mth = GSDescriptionForClassMethod(_myProtocol, aSelector); + } + else + { + mth = [_myProtocol descriptionForClassMethod: aSelector]; + } + } + return mth; + } + return 0; +} + /* * Forwards any message to the delegate if the method is declared in * the checker's protocol; otherwise raises an NSInvalidArgumentException. */ - (void) forwardInvocation: (NSInvocation*)anInvocation { - if (GSObjCIsInstance(_myTarget)) + const char *type; + + if ([self _methodDescription: [anInvocation selector]] == 0) { - if (![_myProtocol descriptionForInstanceMethod: [anInvocation selector]]) + if (GSObjCIsInstance(_myTarget)) { [NSException raise: NSInvalidArgumentException format: @"<%s -%@> not declared", [_myProtocol name], NSStringFromSelector([anInvocation selector])]; } - } - else - { - if (![_myProtocol descriptionForClassMethod: [anInvocation selector]]) + else { [NSException raise: NSInvalidArgumentException format: @"<%s +%@> not declared", @@ -80,6 +119,23 @@ } } [anInvocation invokeWithTarget: _myTarget]; + + /* + * If the method returns 'self' (ie the target object) replace the + * returned value with the protocol checker. + */ + type = [[anInvocation methodSignature] methodReturnType]; + if (strcmp(type, @encode(id)) == 0) + { + id buf; + + [anInvocation getReturnValue: &buf]; + if (buf == _myTarget) + { + buf = self; + [anInvocation setReturnValue: &buf]; + } + } } - (id) init @@ -92,9 +148,10 @@ * Initializes a newly allocated NSProtocolChecker instance that will * forward any messages in the aProtocol protocol to anObject, its * delegate. Thus, the checker can be vended in lieu of anObject to - * restrict the messages that can be sent to anObject. If anObject is - * allowed to be freed or dereferenced by clients, the free method - * should be included in aProtocol. Returns the new instance. + * restrict the messages that can be sent to anObject. If any method + * in the protocol returns anObject, the checker will replace the returned + * value with itsself rather than the target object.
+ * Returns the new instance. */ - (id) initWithTarget: (NSObject*)anObject protocol: (Protocol*)aProtocol { @@ -110,11 +167,105 @@ - (NSMethodSignature*) methodSignatureForSelector: (SEL)aSelector { - if (aSelector == _cmd || [self respondsToSelector: aSelector] == YES) + const char *types; + struct objc_method *mth; + Class c; + + if (aSelector == 0) + [NSException raise: NSInvalidArgumentException + format: @"%@ null selector given", NSStringFromSelector(_cmd)]; + + /* + * Evil hack to prevent recursion - if we are asking a remote + * object for a method signature, we can't ask it for the + * signature of methodSignatureForSelector:, so we hack in + * the signature required manually :-( + */ + if (sel_eq(aSelector, _cmd)) { - return [_myTarget methodSignatureForSelector: aSelector]; + static NSMethodSignature *sig = nil; + + if (sig == nil) + { + sig = [NSMethodSignature signatureWithObjCTypes: "@@::"]; + RETAIN(sig); + } + return sig; } - return nil; + + if (_myProtocol != nil) + { + const char *types = 0; + struct objc_method_description *desc; + + desc = [self _methodDescription: aSelector]; + if (desc != 0) + { + types = desc->types; + } + if (types == 0) + { + return nil; + } + return [NSMethodSignature signatureWithObjCTypes: types]; + } + + c = GSObjCClass(self); + mth = GSGetInstanceMethod(c, aSelector); + if (mth == 0) + { + return nil; // Method not implemented + } + types = mth->method_types; + + /* + * If there are protocols that this class conforms to, + * the method may be listed in a protocol with more + * detailed type information than in the class itsself + * and we must therefore use the information from the + * protocol. + * This is because protocols also carry information + * used by the Distributed Objects system, which the + * runtime does not maintain in classes. + */ + if (c->protocols != 0) + { + struct objc_protocol_list *protocols = c->protocols; + BOOL found = NO; + + while (found == NO && protocols != 0) + { + unsigned i = 0; + + while (found == NO && i < protocols->count) + { + Protocol *p; + struct objc_method_description *pmth; + + p = protocols->list[i++]; + if (c == (Class)self) + { + pmth = [p descriptionForClassMethod: aSelector]; + } + else + { + pmth = [p descriptionForInstanceMethod: aSelector]; + } + if (pmth != 0) + { + types = pmth->types; + found = YES; + } + } + protocols = protocols->next; + } + } + + if (types == 0) + { + return nil; + } + return [NSMethodSignature signatureWithObjCTypes: types]; } /** @@ -126,25 +277,6 @@ return _myProtocol; } -- (BOOL) respondsToSelector: (SEL)aSelector -{ - if (GSObjCIsInstance(_myTarget)) - { - if ([_myProtocol descriptionForInstanceMethod: aSelector]) - { - return YES; - } - } - else - { - if ([_myProtocol descriptionForClassMethod: aSelector]) - { - return YES; - } - } - return NO; -} - /** * Returns the target of the NSProtocolChecker. */