Index: chrome/browser/ui/cocoa/objc_zombie_unittest.mm |
diff --git a/chrome/browser/ui/cocoa/objc_zombie_unittest.mm b/chrome/browser/ui/cocoa/objc_zombie_unittest.mm |
index 69f6ea0315806e2c83df6356598b322e2c3103f7..1cadf4a7888e7565dcc193dca226a393aabb9664 100644 |
--- a/chrome/browser/ui/cocoa/objc_zombie_unittest.mm |
+++ b/chrome/browser/ui/cocoa/objc_zombie_unittest.mm |
@@ -3,12 +3,28 @@ |
// found in the LICENSE file. |
#import <Cocoa/Cocoa.h> |
+#include <dlfcn.h> |
+#include "base/logging.h" |
#import "base/memory/scoped_nsobject.h" |
#import "chrome/browser/ui/cocoa/objc_zombie.h" |
#include "testing/gtest/include/gtest/gtest.h" |
#include "testing/platform_test.h" |
+namespace { |
+ |
+// Dynamically look up |objc_setAssociatedObject()|, which isn't |
+// available until the 10.6 SDK. |
+ |
+typedef void objc_setAssociatedObjectFn(id object, void *key, id value, |
+ int policy); |
+objc_setAssociatedObjectFn* LookupSetAssociatedObjectFn() { |
+ return reinterpret_cast<objc_setAssociatedObjectFn*>( |
+ dlsym(RTLD_DEFAULT, "objc_setAssociatedObject")); |
+} |
+ |
+} // namespace |
+ |
@interface ZombieCxxDestructTest : NSObject |
{ |
scoped_nsobject<id> aRef_; |
@@ -26,13 +42,45 @@ |
} |
@end |
+@interface ZombieAssociatedObjectTest : NSObject |
++ (BOOL)supportsAssociatedObjects; |
+- (id)initWithAssociatedObject:(id)anObject; |
+@end |
+ |
+@implementation ZombieAssociatedObjectTest |
+ |
++ (BOOL)supportsAssociatedObjects { |
+ if (LookupSetAssociatedObjectFn()) |
+ return YES; |
+ return NO; |
+} |
+ |
+- (id)initWithAssociatedObject:(id)anObject { |
+ self = [super init]; |
+ if (self) { |
+ objc_setAssociatedObjectFn* fn = LookupSetAssociatedObjectFn(); |
+ if (fn) { |
+ // Cribbed from 10.6 <objc/runtime.h>. |
+ static const int kObjcAssociationRetain = 01401; |
+ |
+ // The address of the variable itself is the unique key, the |
+ // contents don't matter. |
+ static char kAssociatedObjectKey = 'x'; |
+ |
+ (*fn)(self, &kAssociatedObjectKey, anObject, kObjcAssociationRetain); |
+ } |
+ } |
+ return self; |
+} |
+ |
+@end |
+ |
namespace { |
// Verify that the C++ destructors run when the last reference to the |
-// object is released. Unfortunately, testing the negative requires |
-// commenting out the |object_cxxDestruct()| call in |
-// |ZombieDealloc()|. |
- |
+// object is released. |
+// NOTE(shess): To test the negative, comment out the |g_objectDestruct()| |
+// call in |ZombieDealloc()|. |
TEST(ObjcZombieTest, CxxDestructors) { |
scoped_nsobject<id> anObject([[NSObject alloc] init]); |
EXPECT_EQ(1u, [anObject retainCount]); |
@@ -53,4 +101,34 @@ TEST(ObjcZombieTest, CxxDestructors) { |
EXPECT_EQ(1u, [anObject retainCount]); |
} |
+// Verify that the associated objects are released when the object is |
+// released. |
+// NOTE(shess): To test the negative, hardcode |g_objectDestruct| to |
+// the 10.5 version in |ZombieInit()|, and run this test on 10.6. |
+TEST(ObjcZombieTest, AssociatedObjectsReleased) { |
+ if (![ZombieAssociatedObjectTest supportsAssociatedObjects]) { |
+ LOG(ERROR) |
+ << "ObjcZombieTest.AssociatedObjectsReleased not supported on 10.5"; |
+ return; |
+ } |
+ |
+ scoped_nsobject<id> anObject([[NSObject alloc] init]); |
+ EXPECT_EQ(1u, [anObject retainCount]); |
+ |
+ ASSERT_TRUE(ObjcEvilDoers::ZombieEnable(YES, 100)); |
+ |
+ scoped_nsobject<ZombieAssociatedObjectTest> soonInfected( |
+ [[ZombieAssociatedObjectTest alloc] initWithAssociatedObject:anObject]); |
+ EXPECT_EQ(2u, [anObject retainCount]); |
+ |
+ // When |soonInfected| becomes a zombie, the associated object |
+ // should be released. |
+ soonInfected.reset(); |
+ EXPECT_EQ(1u, [anObject retainCount]); |
+ |
+ // The local reference should remain (associated objects not re-released). |
+ ObjcEvilDoers::ZombieDisable(); |
+ EXPECT_EQ(1u, [anObject retainCount]); |
+} |
+ |
} // namespace |