// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import <Foundation/Foundation.h> #include <objc/runtime.h> #include "base/logging.h" #import "base/mac/scoped_nsobject.h" #import "chrome/common/mac/objc_zombie.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/platform_test.h" @interface ZombieCxxDestructTest : NSObject { base::scoped_nsobject<id> aRef_; } - (id)initWith:(id)anObject; @end @implementation ZombieCxxDestructTest - (id)initWith:(id)anObject { self = [super init]; if (self) { aRef_.reset([anObject retain]); } return self; } @end @interface ZombieAssociatedObjectTest : NSObject - (id)initWithAssociatedObject:(id)anObject; @end @implementation ZombieAssociatedObjectTest - (id)initWithAssociatedObject:(id)anObject { if ((self = [super init])) { // The address of the variable itself is the unique key, the // contents don't matter. static char kAssociatedObjectKey = 'x'; objc_setAssociatedObject( self, &kAssociatedObjectKey, anObject, OBJC_ASSOCIATION_RETAIN); } return self; } @end namespace { // Verify that the C++ destructors run when the last reference to the // object is released. // NOTE(shess): To test the negative, comment out the |g_objectDestruct()| // call in |ZombieDealloc()|. TEST(ObjcZombieTest, CxxDestructors) { base::scoped_nsobject<id> anObject([[NSObject alloc] init]); EXPECT_EQ(1u, [anObject retainCount]); ASSERT_TRUE(ObjcEvilDoers::ZombieEnable(YES, 100)); base::scoped_nsobject<ZombieCxxDestructTest> soonInfected( [[ZombieCxxDestructTest alloc] initWith:anObject]); EXPECT_EQ(2u, [anObject retainCount]); // When |soonInfected| becomes a zombie, the C++ destructors should // run and release a reference to |anObject|. soonInfected.reset(); EXPECT_EQ(1u, [anObject retainCount]); // The local reference should remain (C++ destructors aren't re-run). ObjcEvilDoers::ZombieDisable(); EXPECT_EQ(1u, [anObject retainCount]); } // Verify that the associated objects are released when the object is // released. TEST(ObjcZombieTest, AssociatedObjectsReleased) { base::scoped_nsobject<id> anObject([[NSObject alloc] init]); EXPECT_EQ(1u, [anObject retainCount]); ASSERT_TRUE(ObjcEvilDoers::ZombieEnable(YES, 100)); base::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