Skip to content

Commit d4e85d6

Browse files
GULObjectSwizzler fix dealloc thread safety (#3300)
1 parent 07391f0 commit d4e85d6

File tree

5 files changed

+106
-26
lines changed

5 files changed

+106
-26
lines changed

GoogleUtilities.podspec

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,11 @@ other Google CocoaPods. They're not intended for direct public usage.
7878

7979
s.subspec 'ISASwizzler' do |iss|
8080
iss.source_files = 'GoogleUtilities/ISASwizzler/**/*.[mh]', 'GoogleUtilities/Common/*.h'
81+
iss.public_header_files = 'GoogleUtilities/ISASwizzler/Private/*.h'
8182
iss.private_header_files = 'GoogleUtilities/ISASwizzler/Private/*.h'
83+
84+
# Disable ARC for GULSwizzledObject.
85+
iss.requires_arc = ['GoogleUtilities/Common/*.h', 'GoogleUtilities/ISASwizzler/GULObjectSwizzler*.[mh]']
8286
end
8387

8488
s.subspec 'MethodSwizzler' do |mss|

GoogleUtilities/Example/Tests/Swizzler/GULObjectSwizzlerTest.m

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -323,30 +323,52 @@ - (void)testRespondsToSelectorWorksEvenIfSwizzledProxyIsKVOd {
323323
* swizzles a proxy object that we've also ISA Swizzled.
324324
*/
325325
- (void)testRespondsToSelectorWorksEvenIfSwizzledProxyISASwizzledBySomeoneElse {
326-
NSObject *object = [[NSObject alloc] init];
327-
GULProxy *proxyObject = [GULProxy proxyWithDelegate:object];
326+
Class generatedClass = nil;
327+
328+
@autoreleasepool {
329+
NSObject *object = [[NSObject alloc] init];
330+
GULProxy *proxyObject = [GULProxy proxyWithDelegate:object];
331+
332+
GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc] initWithObject:proxyObject];
333+
[swizzler copySelector:@selector(donorDescription)
334+
fromClass:[GULObjectSwizzlerTest class]
335+
isClassSelector:NO];
336+
[swizzler swizzle];
337+
338+
// Someone else ISA Swizzles the same object after GULObjectSwizzler.
339+
Class originalClass = object_getClass(proxyObject);
340+
NSString *newClassName = [NSString
341+
stringWithFormat:@"gul_test_%p_%@", proxyObject, NSStringFromClass(originalClass)];
342+
generatedClass = objc_allocateClassPair(originalClass, newClassName.UTF8String, 0);
343+
objc_registerClassPair(generatedClass);
344+
object_setClass(proxyObject, generatedClass);
345+
346+
XCTAssertTrue([proxyObject respondsToSelector:@selector(donorDescription)]);
347+
XCTAssertEqual([proxyObject performSelector:@selector(donorDescription)],
348+
@"SwizzledDonorDescription");
349+
}
350+
351+
// A class generated by GULObjectSwizzler must not be disposed if there is its subclass.
352+
XCTAssertNoThrow([generatedClass description]);
328353

329-
GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc] initWithObject:proxyObject];
330-
[swizzler copySelector:@selector(donorDescription)
331-
fromClass:[GULObjectSwizzlerTest class]
332-
isClassSelector:NO];
333-
[swizzler swizzle];
354+
// Clean up.
355+
objc_disposeClassPair(generatedClass);
356+
}
334357

335-
// Someone else ISA Swizzles the same object after GULObjectSwizzler.
336-
Class originalClass = object_getClass(proxyObject);
337-
NSString *newClassName =
338-
[NSString stringWithFormat:@"gul_test_%p_%@", proxyObject, NSStringFromClass(originalClass)];
339-
Class generatedClass = objc_allocateClassPair(originalClass, newClassName.UTF8String, 0);
340-
objc_registerClassPair(generatedClass);
341-
object_setClass(proxyObject, generatedClass);
358+
- (void)testMultiSwizzling {
359+
NSObject *object = [[NSObject alloc] init];
342360

343-
XCTAssertTrue([proxyObject respondsToSelector:@selector(donorDescription)]);
344-
XCTAssertEqual([proxyObject performSelector:@selector(donorDescription)],
345-
@"SwizzledDonorDescription");
361+
NSInteger swizzleCount = 10;
362+
for (NSInteger i = 0; i < swizzleCount; i++) {
363+
GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc] initWithObject:object];
364+
[swizzler copySelector:@selector(donorDescription)
365+
fromClass:[GULObjectSwizzlerTest class]
366+
isClassSelector:NO];
367+
[swizzler swizzle];
368+
}
346369

347-
// Clean up.
348-
object_setClass(proxyObject, originalClass);
349-
objc_disposeClassPair(generatedClass);
370+
XCTAssertNoThrow([object performSelector:@selector(donorDescription)]);
371+
object = nil;
350372
}
351373

352374
@end
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2019 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#import <GoogleUtilities/GULObjectSwizzler.h>
16+
17+
@interface GULObjectSwizzler (Internal)
18+
19+
- (void)swizzledObjectHasBeenDeallocatedWithGeneratedSubclass:(BOOL)isInstanceOfGeneratedSubclass;
20+
21+
@end

GoogleUtilities/ISASwizzler/GULObjectSwizzler.m

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
#import "Private/GULObjectSwizzler.h"
15+
#import <GoogleUtilities/GULObjectSwizzler.h>
1616

1717
#import <objc/runtime.h>
1818

@@ -81,8 +81,8 @@ - (instancetype)initWithObject:(id)object {
8181
if (swizzledObject) {
8282
_swizzledObject = swizzledObject;
8383
_originalClass = object_getClass(object);
84-
NSString *newClassName = [NSString
85-
stringWithFormat:@"fir_%p_%@", swizzledObject, NSStringFromClass(_originalClass)];
84+
NSString *newClassName = [NSString stringWithFormat:@"fir_%@_%@", [[NSUUID UUID] UUIDString],
85+
NSStringFromClass(_originalClass)];
8686
_generatedClass = objc_allocateClassPair(_originalClass, newClassName.UTF8String, 0);
8787
NSAssert(_generatedClass, @"Wasn't able to allocate the class pair.");
8888
} else {
@@ -150,8 +150,17 @@ - (void)swizzle {
150150
}
151151
}
152152

153-
- (void)dealloc {
154-
objc_disposeClassPair(_generatedClass);
153+
- (void)swizzledObjectHasBeenDeallocatedWithGeneratedSubclass:(BOOL)isInstanceOfGeneratedSubclass {
154+
// If the swizzled object had a different class, it most likely indicates that the object was
155+
// ISA swizzled one more time. In this case it is not safe to dispose the generated class. We
156+
// will have to keep it to prevent a crash.
157+
158+
// TODO: Consider adding a flag that can be set by the host application to dispose the class pair
159+
// unconditionally. It may be used by apps that use ISA Swizzling themself and are confident in
160+
// disposing their subclasses.
161+
if (isInstanceOfGeneratedSubclass) {
162+
objc_disposeClassPair(_generatedClass);
163+
}
155164
}
156165

157166
- (BOOL)isSwizzlingProxyObject {

GoogleUtilities/ISASwizzler/GULSwizzledObject.m

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
#import <objc/runtime.h>
1616

17-
#import "Private/GULObjectSwizzler.h"
17+
#import "GULObjectSwizzler+Internal.h"
1818
#import "Private/GULSwizzledObject.h"
1919

2020
NSString *kSwizzlerAssociatedObjectKey = @"gul_objectSwizzler";
@@ -28,6 +28,7 @@ @implementation GULSwizzledObject
2828
+ (void)copyDonorSelectorsUsingObjectSwizzler:(GULObjectSwizzler *)objectSwizzler {
2929
[objectSwizzler copySelector:@selector(gul_objectSwizzler) fromClass:self isClassSelector:NO];
3030
[objectSwizzler copySelector:@selector(gul_class) fromClass:self isClassSelector:NO];
31+
[objectSwizzler copySelector:@selector(dealloc) fromClass:self isClassSelector:NO];
3132

3233
// This is needed because NSProxy objects usually override -[NSObjectProtocol respondsToSelector:]
3334
// and ask this question to the underlying object. Since we don't swizzle the underlying object
@@ -61,4 +62,27 @@ - (BOOL)respondsToSelector:(SEL)aSelector {
6162
return [gulClass instancesRespondToSelector:aSelector] || [super respondsToSelector:aSelector];
6263
}
6364

65+
- (void)dealloc {
66+
// We need to make sure the swizzler is deallocated after the swizzled object to do the clean up
67+
// only when the swizzled object is not used.
68+
GULObjectSwizzler *swizzler = nil;
69+
BOOL isInstanceOfGeneratedClass = NO;
70+
71+
@autoreleasepool {
72+
Class generatedClass = [self gul_class];
73+
isInstanceOfGeneratedClass = object_getClass(self) == generatedClass;
74+
75+
swizzler = [[self gul_objectSwizzler] retain];
76+
[GULObjectSwizzler setAssociatedObject:self
77+
key:kSwizzlerAssociatedObjectKey
78+
value:nil
79+
association:GUL_ASSOCIATION_RETAIN_NONATOMIC];
80+
}
81+
82+
[super dealloc];
83+
84+
[swizzler swizzledObjectHasBeenDeallocatedWithGeneratedSubclass:isInstanceOfGeneratedClass];
85+
[swizzler release];
86+
}
87+
6488
@end

0 commit comments

Comments
 (0)