/
FBClassStrongLayout.mm
211 lines (162 loc) · 6.69 KB
/
FBClassStrongLayout.mm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
/**
* Copyright (c) 2016-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "FBClassStrongLayout.h"
#import <math.h>
#import <memory>
#import <objc/runtime.h>
#import <vector>
#import <UIKit/UIKit.h>
#import "FBIvarReference.h"
#import "FBObjectInStructReference.h"
#import "FBStructEncodingParser.h"
#import "Struct.h"
#import "Type.h"
/**
If we stumble upon a struct, we need to go through it and check if it doesn't retain some objects.
*/
static NSArray *FBGetReferencesForObjectsInStructEncoding(FBIvarReference *ivar, std::string encoding) {
NSMutableArray<FBObjectInStructReference *> *references = [NSMutableArray new];
std::string ivarName = std::string([ivar.name cStringUsingEncoding:NSUTF8StringEncoding]);
FB::RetainCycleDetector::Parser::Struct parsedStruct =
FB::RetainCycleDetector::Parser::parseStructEncodingWithName(encoding, ivarName);
std::vector<std::shared_ptr<FB::RetainCycleDetector::Parser::Type>> types = parsedStruct.flattenTypes();
ptrdiff_t offset = ivar.offset;
for (auto &type: types) {
NSUInteger size, align;
std::string typeEncoding = type->typeEncoding;
if (typeEncoding[0] == '^') {
// It's a pointer, let's skip
size = sizeof(void *);
align = _Alignof(void *);
} else {
@try {
NSGetSizeAndAlignment(typeEncoding.c_str(),
&size,
&align);
} @catch (NSException *e) {
/**
If we failed, we probably have C++ and ObjC cannot get it's size and alignment. We are skipping.
If we would like to support it, we would need to derive size and alignment of type from the string.
C++ does not have reflection so we can't really do that unless we create the mapping ourselves.
*/
break;
}
}
// The object must be aligned
NSUInteger overAlignment = offset % align;
NSUInteger whatsMissing = (overAlignment == 0) ? 0 : align - overAlignment;
offset += whatsMissing;
if (typeEncoding[0] == '@') {
// The index that ivar layout will ask for is going to be aligned with pointer size
// Prepare additional context
NSString *typeEncodingName = [NSString stringWithCString:type->name.c_str() encoding:NSUTF8StringEncoding];
NSMutableArray *namePath = [NSMutableArray new];
for (auto &name: type->typePath) {
NSString *nameString = [NSString stringWithCString:name.c_str() encoding:NSUTF8StringEncoding];
if (nameString) {
[namePath addObject:nameString];
}
}
if (typeEncodingName) {
[namePath addObject:typeEncodingName];
}
[references addObject:[[FBObjectInStructReference alloc] initWithIndex:(offset / sizeof(void *))
namePath:namePath]];
}
offset += size;
}
return references;
}
NSArray<id<FBObjectReference>> *FBGetClassReferences(Class aCls) {
NSMutableArray<id<FBObjectReference>> *result = [NSMutableArray new];
unsigned int count;
Ivar *ivars = class_copyIvarList(aCls, &count);
for (unsigned int i = 0; i < count; ++i) {
Ivar ivar = ivars[i];
FBIvarReference *wrapper = [[FBIvarReference alloc] initWithIvar:ivar];
if (wrapper.type == FBStructType) {
std::string encoding = std::string(ivar_getTypeEncoding(wrapper.ivar));
NSArray<FBObjectInStructReference *> *references = FBGetReferencesForObjectsInStructEncoding(wrapper, encoding);
[result addObjectsFromArray:references];
} else {
[result addObject:wrapper];
}
}
free(ivars);
return [result copy];
}
static NSIndexSet *FBGetLayoutAsIndexesForDescription(NSUInteger minimumIndex, const uint8_t *layoutDescription) {
NSMutableIndexSet *interestingIndexes = [NSMutableIndexSet new];
NSUInteger currentIndex = minimumIndex;
while (*layoutDescription != '\x00') {
int upperNibble = (*layoutDescription & 0xf0) >> 4;
int lowerNibble = *layoutDescription & 0xf;
// Upper nimble is for skipping
currentIndex += upperNibble;
// Lower nimble describes count
[interestingIndexes addIndexesInRange:NSMakeRange(currentIndex, lowerNibble)];
currentIndex += lowerNibble;
++layoutDescription;
}
return interestingIndexes;
}
static NSUInteger FBGetMinimumIvarIndex(__unsafe_unretained Class aCls) {
NSUInteger minimumIndex = 1;
unsigned int count;
Ivar *ivars = class_copyIvarList(aCls, &count);
if (count > 0) {
Ivar ivar = ivars[0];
ptrdiff_t offset = ivar_getOffset(ivar);
minimumIndex = offset / (sizeof(void *));
}
free(ivars);
return minimumIndex;
}
static NSArray<id<FBObjectReference>> *FBGetStrongReferencesForClass(Class aCls) {
NSArray<id<FBObjectReference>> *ivars = [FBGetClassReferences(aCls) filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
if ([evaluatedObject isKindOfClass:[FBIvarReference class]]) {
FBIvarReference *wrapper = evaluatedObject;
return wrapper.type != FBUnknownType;
}
return YES;
}]];
const uint8_t *fullLayout = class_getIvarLayout(aCls);
if (!fullLayout) {
return @[];
}
NSUInteger minimumIndex = FBGetMinimumIvarIndex(aCls);
NSIndexSet *parsedLayout = FBGetLayoutAsIndexesForDescription(minimumIndex, fullLayout);
NSArray<id<FBObjectReference>> *filteredIvars =
[ivars filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id<FBObjectReference> evaluatedObject,
NSDictionary *bindings) {
return [parsedLayout containsIndex:[evaluatedObject indexInIvarLayout]];
}]];
return filteredIvars;
}
NSArray<id<FBObjectReference>> *FBGetObjectStrongReferences(id obj,
NSMutableDictionary<Class, NSArray<id<FBObjectReference>> *> *layoutCache) {
NSMutableArray<id<FBObjectReference>> *array = [NSMutableArray new];
__unsafe_unretained Class previousClass = nil;
__unsafe_unretained Class currentClass = object_getClass(obj);
while (previousClass != currentClass) {
NSArray<id<FBObjectReference>> *ivars;
if (layoutCache && currentClass) {
ivars = layoutCache[currentClass];
}
if (!ivars) {
ivars = FBGetStrongReferencesForClass(currentClass);
if (layoutCache && currentClass) {
layoutCache[(id<NSCopying>)currentClass] = ivars;
}
}
[array addObjectsFromArray:ivars];
previousClass = currentClass;
currentClass = class_getSuperclass(currentClass);
}
return [array copy];
}