| // 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 |