| // Copyright 2016 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. | 
 |  | 
 | #include "base/mac/objc_release_properties.h" | 
 | #include "base/stl_util.h" | 
 |  | 
 | #import "base/mac/scoped_nsautorelease_pool.h" | 
 | #include "testing/gtest/include/gtest/gtest.h" | 
 |  | 
 | #import <objc/runtime.h> | 
 |  | 
 | // "When I'm alone, I count myself." | 
 | //   --Count von Count, http://www.youtube.com/watch?v=FKzszqa9WA4 | 
 |  | 
 | namespace { | 
 |  | 
 | // The number of CountVonCounts outstanding. | 
 | int ah_ah_ah; | 
 |  | 
 | // NumberHolder exists to exercise the property attribute string parser by | 
 | // providing a named struct and an anonymous union. | 
 | struct NumberHolder { | 
 |   union { | 
 |     long long sixty_four; | 
 |     int thirty_two; | 
 |     short sixteen; | 
 |     char eight; | 
 |   } what; | 
 |   enum { SIXTY_FOUR, THIRTY_TWO, SIXTEEN, EIGHT } how; | 
 | }; | 
 |  | 
 | }  // namespace | 
 |  | 
 | @interface CountVonCount : NSObject<NSCopying> | 
 |  | 
 | + (CountVonCount*)countVonCount; | 
 |  | 
 | @end  // @interface CountVonCount | 
 |  | 
 | @implementation CountVonCount | 
 |  | 
 | + (CountVonCount*)countVonCount { | 
 |   return [[[CountVonCount alloc] init] autorelease]; | 
 | } | 
 |  | 
 | - (id)init { | 
 |   ++ah_ah_ah; | 
 |   return [super init]; | 
 | } | 
 |  | 
 | - (void)dealloc { | 
 |   --ah_ah_ah; | 
 |   [super dealloc]; | 
 | } | 
 |  | 
 | - (id)copyWithZone:(NSZone*)zone { | 
 |   return [[CountVonCount allocWithZone:zone] init]; | 
 | } | 
 |  | 
 | @end  // @implementation CountVonCount | 
 |  | 
 | @interface ObjCPropertyTestBase : NSObject { | 
 |  @private | 
 |   CountVonCount* baseCvcRetain_; | 
 |   CountVonCount* baseCvcCopy_; | 
 |   CountVonCount* baseCvcAssign_; | 
 |   CountVonCount* baseCvcNotProperty_; | 
 |   CountVonCount* baseCvcNil_; | 
 |   CountVonCount* baseCvcCustom_; | 
 |   int baseInt_; | 
 |   double baseDouble_; | 
 |   void* basePointer_; | 
 |   NumberHolder baseStruct_; | 
 | } | 
 |  | 
 | @property(retain, nonatomic) CountVonCount* baseCvcRetain; | 
 | @property(copy, nonatomic) CountVonCount* baseCvcCopy; | 
 | @property(assign, nonatomic) CountVonCount* baseCvcAssign; | 
 | @property(retain, nonatomic) CountVonCount* baseCvcNil; | 
 | @property(retain, nonatomic, getter=baseCustom, setter=setBaseCustom:) | 
 |     CountVonCount* baseCvcCustom; | 
 | @property(readonly, retain, nonatomic) CountVonCount* baseCvcReadOnly; | 
 | @property(retain, nonatomic) CountVonCount* baseCvcDynamic; | 
 | @property(assign, nonatomic) int baseInt; | 
 | @property(assign, nonatomic) double baseDouble; | 
 | @property(assign, nonatomic) void* basePointer; | 
 | @property(assign, nonatomic) NumberHolder baseStruct; | 
 |  | 
 | - (void)setBaseCvcNotProperty:(CountVonCount*)cvc; | 
 |  | 
 | @end  // @interface ObjCPropertyTestBase | 
 |  | 
 | @implementation ObjCPropertyTestBase | 
 |  | 
 | @synthesize baseCvcRetain = baseCvcRetain_; | 
 | @synthesize baseCvcCopy = baseCvcCopy_; | 
 | @synthesize baseCvcAssign = baseCvcAssign_; | 
 | @synthesize baseCvcNil = baseCvcNil_; | 
 | @synthesize baseCvcCustom = baseCvcCustom_; | 
 | @synthesize baseCvcReadOnly = baseCvcReadOnly_; | 
 | @dynamic baseCvcDynamic; | 
 | @synthesize baseInt = baseInt_; | 
 | @synthesize baseDouble = baseDouble_; | 
 | @synthesize basePointer = basePointer_; | 
 | @synthesize baseStruct = baseStruct_; | 
 |  | 
 | - (void)dealloc { | 
 |   [baseCvcNotProperty_ release]; | 
 |   base::mac::ReleaseProperties(self); | 
 |   [super dealloc]; | 
 | } | 
 |  | 
 | - (void)setBaseCvcNotProperty:(CountVonCount*)cvc { | 
 |   if (cvc != baseCvcNotProperty_) { | 
 |     [baseCvcNotProperty_ release]; | 
 |     baseCvcNotProperty_ = [cvc retain]; | 
 |   } | 
 | } | 
 |  | 
 | - (void)setBaseCvcReadOnlyProperty:(CountVonCount*)cvc { | 
 |   if (cvc != baseCvcReadOnly_) { | 
 |     [baseCvcReadOnly_ release]; | 
 |     baseCvcReadOnly_ = [cvc retain]; | 
 |   } | 
 | } | 
 |  | 
 | @end  // @implementation ObjCPropertyTestBase | 
 |  | 
 | @protocol ObjCPropertyTestProtocol | 
 |  | 
 | @property(retain, nonatomic) CountVonCount* protoCvcRetain; | 
 | @property(copy, nonatomic) CountVonCount* protoCvcCopy; | 
 | @property(assign, nonatomic) CountVonCount* protoCvcAssign; | 
 | @property(retain, nonatomic) CountVonCount* protoCvcNil; | 
 | @property(retain, nonatomic, getter=protoCustom, setter=setProtoCustom:) | 
 |     CountVonCount* protoCvcCustom; | 
 | @property(retain, nonatomic) CountVonCount* protoCvcDynamic; | 
 | @property(assign, nonatomic) int protoInt; | 
 | @property(assign, nonatomic) double protoDouble; | 
 | @property(assign, nonatomic) void* protoPointer; | 
 | @property(assign, nonatomic) NumberHolder protoStruct; | 
 |  | 
 | @end  // @protocol ObjCPropertyTestProtocol | 
 |  | 
 | // @protocol(NSObject) declares some (copy, readonly) properties (superclass, | 
 | // description, debugDescription, and hash), but we're not expected to release | 
 | // them. The current implementation only releases properties backed by instance | 
 | // variables, and this makes sure that doesn't change in a breaking way. | 
 | @interface ObjCPropertyTestDerived | 
 |     : ObjCPropertyTestBase<ObjCPropertyTestProtocol, NSObject> { | 
 |  @private | 
 |   CountVonCount* derivedCvcRetain_; | 
 |   CountVonCount* derivedCvcCopy_; | 
 |   CountVonCount* derivedCvcAssign_; | 
 |   CountVonCount* derivedCvcNotProperty_; | 
 |   CountVonCount* derivedCvcNil_; | 
 |   CountVonCount* derivedCvcCustom_; | 
 |   int derivedInt_; | 
 |   double derivedDouble_; | 
 |   void* derivedPointer_; | 
 |   NumberHolder derivedStruct_; | 
 |  | 
 |   CountVonCount* protoCvcRetain_; | 
 |   CountVonCount* protoCvcCopy_; | 
 |   CountVonCount* protoCvcAssign_; | 
 |   CountVonCount* protoCvcNil_; | 
 |   CountVonCount* protoCvcCustom_; | 
 |   int protoInt_; | 
 |   double protoDouble_; | 
 |   void* protoPointer_; | 
 |   NumberHolder protoStruct_; | 
 | } | 
 |  | 
 | @property(retain, nonatomic) CountVonCount* derivedCvcRetain; | 
 | @property(copy, nonatomic) CountVonCount* derivedCvcCopy; | 
 | @property(assign, nonatomic) CountVonCount* derivedCvcAssign; | 
 | @property(retain, nonatomic) CountVonCount* derivedCvcNil; | 
 | @property(retain, nonatomic, getter=derivedCustom, setter=setDerivedCustom:) | 
 |     CountVonCount* derivedCvcCustom; | 
 | @property(retain, nonatomic) CountVonCount* derivedCvcDynamic; | 
 | @property(assign, nonatomic) int derivedInt; | 
 | @property(assign, nonatomic) double derivedDouble; | 
 | @property(assign, nonatomic) void* derivedPointer; | 
 | @property(assign, nonatomic) NumberHolder derivedStruct; | 
 |  | 
 | - (void)setDerivedCvcNotProperty:(CountVonCount*)cvc; | 
 |  | 
 | @end  // @interface ObjCPropertyTestDerived | 
 |  | 
 | @implementation ObjCPropertyTestDerived | 
 |  | 
 | @synthesize derivedCvcRetain = derivedCvcRetain_; | 
 | @synthesize derivedCvcCopy = derivedCvcCopy_; | 
 | @synthesize derivedCvcAssign = derivedCvcAssign_; | 
 | @synthesize derivedCvcNil = derivedCvcNil_; | 
 | @synthesize derivedCvcCustom = derivedCvcCustom_; | 
 | @dynamic derivedCvcDynamic; | 
 | @synthesize derivedInt = derivedInt_; | 
 | @synthesize derivedDouble = derivedDouble_; | 
 | @synthesize derivedPointer = derivedPointer_; | 
 | @synthesize derivedStruct = derivedStruct_; | 
 |  | 
 | @synthesize protoCvcRetain = protoCvcRetain_; | 
 | @synthesize protoCvcCopy = protoCvcCopy_; | 
 | @synthesize protoCvcAssign = protoCvcAssign_; | 
 | @synthesize protoCvcNil = protoCvcNil_; | 
 | @synthesize protoCvcCustom = protoCvcCustom_; | 
 | @dynamic protoCvcDynamic; | 
 | @synthesize protoInt = protoInt_; | 
 | @synthesize protoDouble = protoDouble_; | 
 | @synthesize protoPointer = protoPointer_; | 
 | @synthesize protoStruct = protoStruct_; | 
 |  | 
 | + (BOOL)resolveInstanceMethod:(SEL)sel { | 
 |   static const std::vector<SEL> dynamicMethods { | 
 |     @selector(baseCvcDynamic), @selector(derivedCvcDynamic), | 
 |         @selector(protoCvcDynamic), | 
 |   }; | 
 |   if (!base::ContainsValue(dynamicMethods, sel)) { | 
 |     return NO; | 
 |   } | 
 |   id (*imp)() = []() -> id { return nil; }; | 
 |   class_addMethod([self class], sel, reinterpret_cast<IMP>(imp), "@@:"); | 
 |   return YES; | 
 | } | 
 |  | 
 | - (void)dealloc { | 
 |   base::mac::ReleaseProperties(self); | 
 |   [derivedCvcNotProperty_ release]; | 
 |   [super dealloc]; | 
 | } | 
 |  | 
 | - (void)setDerivedCvcNotProperty:(CountVonCount*)cvc { | 
 |   if (cvc != derivedCvcNotProperty_) { | 
 |     [derivedCvcNotProperty_ release]; | 
 |     derivedCvcNotProperty_ = [cvc retain]; | 
 |   } | 
 | } | 
 |  | 
 | @end  // @implementation ObjCPropertyTestDerived | 
 |  | 
 | @interface ObjcPropertyTestEmpty : NSObject | 
 | @end | 
 |  | 
 | @implementation ObjcPropertyTestEmpty | 
 |  | 
 | - (void)dealloc { | 
 |   base::mac::ReleaseProperties(self); | 
 |   [super dealloc]; | 
 | } | 
 |  | 
 | @end  // @implementation ObjcPropertyTestEmpty | 
 |  | 
 | namespace { | 
 |  | 
 | TEST(ObjCReleasePropertiesTest, SesameStreet) { | 
 |   ObjCPropertyTestDerived* test_object = [[ObjCPropertyTestDerived alloc] init]; | 
 |  | 
 |   // Assure a clean slate. | 
 |   EXPECT_EQ(0, ah_ah_ah); | 
 |   EXPECT_EQ(1U, [test_object retainCount]); | 
 |  | 
 |   CountVonCount* baseAssign = [[CountVonCount alloc] init]; | 
 |   CountVonCount* derivedAssign = [[CountVonCount alloc] init]; | 
 |   CountVonCount* protoAssign = [[CountVonCount alloc] init]; | 
 |  | 
 |   // Make sure that worked before things get more involved. | 
 |   EXPECT_EQ(3, ah_ah_ah); | 
 |  | 
 |   { | 
 |     base::mac::ScopedNSAutoreleasePool pool; | 
 |  | 
 |     test_object.baseCvcRetain = [CountVonCount countVonCount]; | 
 |     test_object.baseCvcCopy = [CountVonCount countVonCount]; | 
 |     test_object.baseCvcAssign = baseAssign; | 
 |     test_object.baseCvcCustom = [CountVonCount countVonCount]; | 
 |     [test_object setBaseCvcReadOnlyProperty:[CountVonCount countVonCount]]; | 
 |     [test_object setBaseCvcNotProperty:[CountVonCount countVonCount]]; | 
 |  | 
 |     // That added 5 objects, plus 1 more that was copied. | 
 |     EXPECT_EQ(9, ah_ah_ah); | 
 |  | 
 |     test_object.derivedCvcRetain = [CountVonCount countVonCount]; | 
 |     test_object.derivedCvcCopy = [CountVonCount countVonCount]; | 
 |     test_object.derivedCvcAssign = derivedAssign; | 
 |     test_object.derivedCvcCustom = [CountVonCount countVonCount]; | 
 |     [test_object setDerivedCvcNotProperty:[CountVonCount countVonCount]]; | 
 |  | 
 |     // That added 4 objects, plus 1 more that was copied. | 
 |     EXPECT_EQ(14, ah_ah_ah); | 
 |  | 
 |     test_object.protoCvcRetain = [CountVonCount countVonCount]; | 
 |     test_object.protoCvcCopy = [CountVonCount countVonCount]; | 
 |     test_object.protoCvcAssign = protoAssign; | 
 |     test_object.protoCvcCustom = [CountVonCount countVonCount]; | 
 |  | 
 |     // That added 3 objects, plus 1 more that was copied. | 
 |     EXPECT_EQ(18, ah_ah_ah); | 
 |   } | 
 |  | 
 |   // Now that the autorelease pool has been popped, the 3 objects that were | 
 |   // copied when placed into the test object will have been deallocated. | 
 |   EXPECT_EQ(15, ah_ah_ah); | 
 |  | 
 |   // Make sure that the setters wo/rk and have the expected semantics. | 
 |   test_object.baseCvcRetain = nil; | 
 |   test_object.baseCvcCopy = nil; | 
 |   test_object.baseCvcAssign = nil; | 
 |   test_object.baseCvcCustom = nil; | 
 |   test_object.derivedCvcRetain = nil; | 
 |   test_object.derivedCvcCopy = nil; | 
 |   test_object.derivedCvcAssign = nil; | 
 |   test_object.derivedCvcCustom = nil; | 
 |   test_object.protoCvcRetain = nil; | 
 |   test_object.protoCvcCopy = nil; | 
 |   test_object.protoCvcAssign = nil; | 
 |   test_object.protoCvcCustom = nil; | 
 |  | 
 |   // The CountVonCounts marked "retain" and "copy" should have been | 
 |   // deallocated. Those marked assign should not have been. The only ones that | 
 |   // should exist now are the ones marked "assign", the ones held in | 
 |   // non-property instance variables, and the ones held in properties marked | 
 |   // readonly. | 
 |   EXPECT_EQ(6, ah_ah_ah); | 
 |  | 
 |   { | 
 |     base::mac::ScopedNSAutoreleasePool pool; | 
 |  | 
 |     // Put things back to how they were. | 
 |     test_object.baseCvcRetain = [CountVonCount countVonCount]; | 
 |     test_object.baseCvcCopy = [CountVonCount countVonCount]; | 
 |     test_object.baseCvcAssign = baseAssign; | 
 |     test_object.baseCvcCustom = [CountVonCount countVonCount]; | 
 |     test_object.derivedCvcRetain = [CountVonCount countVonCount]; | 
 |     test_object.derivedCvcCopy = [CountVonCount countVonCount]; | 
 |     test_object.derivedCvcAssign = derivedAssign; | 
 |     test_object.derivedCvcCustom = [CountVonCount countVonCount]; | 
 |     test_object.protoCvcRetain = [CountVonCount countVonCount]; | 
 |     test_object.protoCvcCopy = [CountVonCount countVonCount]; | 
 |     test_object.protoCvcAssign = protoAssign; | 
 |     test_object.protoCvcCustom = [CountVonCount countVonCount]; | 
 |  | 
 |     // 9 more CountVonCounts, 3 of which were copied. | 
 |     EXPECT_EQ(18, ah_ah_ah); | 
 |   } | 
 |  | 
 |   // Now that the autorelease pool has been popped, the 3 copies are gone. | 
 |   EXPECT_EQ(15, ah_ah_ah); | 
 |  | 
 |   // Releasing the test object should get rid of everything that it owns. | 
 |   [test_object release]; | 
 |  | 
 |   // base::mac::ReleaseProperties(self) should have released all of the | 
 |   // CountVonCounts associated with properties marked "retain" or "copy". The | 
 |   // -dealloc methods in each should have released the single non-property | 
 |   // objects in each. Only the CountVonCounts assigned to the properties marked | 
 |   // "assign" should remain. | 
 |   EXPECT_EQ(3, ah_ah_ah); | 
 |  | 
 |   [baseAssign release]; | 
 |   [derivedAssign release]; | 
 |   [protoAssign release]; | 
 |  | 
 |   // Zero! Zero counts! Ah, ah, ah. | 
 |   EXPECT_EQ(0, ah_ah_ah); | 
 | } | 
 |  | 
 | TEST(ObjCReleasePropertiesTest, EmptyObject) { | 
 |   // Test that ReleaseProperties doesn't do anything unexpected to a class | 
 |   // with no properties. | 
 |   [[[ObjcPropertyTestEmpty alloc] init] release]; | 
 | } | 
 |  | 
 | }  // namespace |