Dave Vasilevsky
djvas****@gmail*****
Sat Dec 15 11:02:33 JST 2007
Hi, I've been overriding methods of Objective-C classes with ruby methods, and encountered some weird behavior. It turns out that RubyCocoa isn't checking whether the method being overridden belongs to the class at hand. If the method actually is inherited, RubyCocoa ends up changing not only this class, but some of its ancestors as well. The solution is to detect whether or not the method is inherited. If it is, don't do a direct override, instead add a method with the new behavior. Patch with test case follows. Cheers, Dave Index: framework/src/objc/OverrideMixin.m =================================================================== --- framework/src/objc/OverrideMixin.m (revision 2158) +++ framework/src/objc/OverrideMixin.m (working copy) @@ -390,8 +390,28 @@ OVMIX_LOG("Already registered Ruby method by selector '%s' types '%s', skipping...", (char *)method, me_types); return; } + +#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4 + if (direct_override) { + // It's only ok to use setImplementation if this method is in our own + // class--otherwise it will change the behavior of our ancestors. + Method *meth_list, *iter; + BOOL ok = NO; + unsigned int count = 0; + + // Search our class' methods + iter = meth_list = class_copyMethodList(klass, &count); + for (; iter && count; ++iter, --count) { + if (sel_isEqual(method_getName(*iter), me_name)) { + ok = YES; + break; + } + } + if (!ok) + direct_override = NO; + free(meth_list); + } -#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4 if (direct_override) method_setImplementation(me, imp); else Index: tests/tc_ovmix.rb =================================================================== --- tests/tc_ovmix.rb (revision 2158) +++ tests/tc_ovmix.rb (working copy) @@ -101,6 +101,12 @@ end end +class OSX::DirectOverrideChild + def overrideMe + 'bar' + end +end + class OSX::NSObject def self.mySuperClassMethod 'bar' @@ -145,6 +151,22 @@ OSX::DirectOverride.checkOverridenMethods end + def test_direct_inheritance + assert(OSX::DirectOverrideParent.ancestors.include?(OSX::NSObject)) + p = OSX::DirectOverrideParent.alloc.init + assert_kind_of(OSX::NSString, p.performSelector('overrideMe')) + assert_equal('foo', p.performSelector('overrideMe').to_s) + p.checkOverride('foo') + + assert(OSX::DirectOverrideChild.ancestors.include?(OSX::NSObject)) + assert(OSX::DirectOverrideChild.ancestors.include?( + OSX::DirectOverrideParent)) + c = OSX::DirectOverrideChild.alloc.init + assert_kind_of(OSX::NSString, c.performSelector('overrideMe')) + assert_equal('bar', c.performSelector('overrideMe').to_s) + c.checkOverride('bar') + end + def test_super_method o = OSX::NSString.stringWithCString('blah') assert_equal('foo', o.mySuperMethod.to_s) Index: tests/objc_test.m =================================================================== --- tests/objc_test.m (revision 2158) +++ tests/objc_test.m (working copy) @@ -393,6 +393,37 @@ @end + +// This needs to be separate from DirectOverride since the test might damage +// this class. + at interface DirectOverrideParent : NSObject + at end + + at implementation DirectOverrideParent + +- (id)overrideMe +{ + return @"foo"; +} + +- (void)checkOverride:(NSString *)want +{ + id obj = [self overrideMe]; + + if (![obj isEqualToString:want]) + [NSException raise:@"DirectOverrideInheritance" + format:@"assertion overrideMe failed, got %@", obj]; +} + + at end + + at interface DirectOverrideChild : DirectOverrideParent + at end + + at implementation DirectOverrideChild + at end + + #import <AddressBook/ABPeoplePickerC.h> @interface TestFourCharCode : NSObject