]>
Commit | Line | Data |
---|---|---|
13ba007e A |
1 | // Define FOUNDATION=1 for NSObject and NSAutoreleasePool |
2 | // Define FOUNDATION=0 for _objc_root* and _objc_autoreleasePool* | |
3 | ||
4 | #include "test.h" | |
5 | ||
6 | #if FOUNDATION | |
7 | # define RR_PUSH() [[NSAutoreleasePool alloc] init] | |
8 | # define RR_POP(p) [(id)p release] | |
9 | # define RR_RETAIN(o) [o retain] | |
10 | # define RR_RELEASE(o) [o release] | |
11 | # define RR_AUTORELEASE(o) [o autorelease] | |
12 | # define RR_RETAINCOUNT(o) [o retainCount] | |
13 | #else | |
14 | # define RR_PUSH() _objc_autoreleasePoolPush() | |
15 | # define RR_POP(p) _objc_autoreleasePoolPop(p) | |
16 | # define RR_RETAIN(o) _objc_rootRetain((id)o) | |
17 | # define RR_RELEASE(o) _objc_rootRelease((id)o) | |
18 | # define RR_AUTORELEASE(o) _objc_rootAutorelease((id)o) | |
19 | # define RR_RETAINCOUNT(o) _objc_rootRetainCount((id)o) | |
20 | #endif | |
21 | ||
22 | #include <objc/objc-internal.h> | |
23 | #include <Foundation/Foundation.h> | |
24 | ||
25 | static int state; | |
26 | static pthread_attr_t smallstack; | |
27 | ||
28 | #define NESTED_COUNT 8 | |
29 | ||
30 | @interface Deallocator : NSObject @end | |
31 | @implementation Deallocator | |
32 | -(void) dealloc | |
33 | { | |
34 | // testprintf("-[Deallocator %p dealloc]\n", self); | |
35 | state++; | |
36 | [super dealloc]; | |
37 | } | |
38 | @end | |
39 | ||
40 | @interface AutoreleaseDuringDealloc : NSObject @end | |
41 | @implementation AutoreleaseDuringDealloc | |
42 | -(void) dealloc | |
43 | { | |
44 | state++; | |
45 | RR_AUTORELEASE([[Deallocator alloc] init]); | |
46 | [super dealloc]; | |
47 | } | |
48 | @end | |
49 | ||
50 | @interface AutoreleasePoolDuringDealloc : NSObject @end | |
51 | @implementation AutoreleasePoolDuringDealloc | |
52 | -(void) dealloc | |
53 | { | |
54 | // caller's pool | |
55 | for (int i = 0; i < NESTED_COUNT; i++) { | |
56 | RR_AUTORELEASE([[Deallocator alloc] init]); | |
57 | } | |
58 | ||
59 | // local pool, popped | |
60 | void *pool = RR_PUSH(); | |
61 | for (int i = 0; i < NESTED_COUNT; i++) { | |
62 | RR_AUTORELEASE([[Deallocator alloc] init]); | |
63 | } | |
64 | RR_POP(pool); | |
65 | ||
66 | // caller's pool again | |
67 | for (int i = 0; i < NESTED_COUNT; i++) { | |
68 | RR_AUTORELEASE([[Deallocator alloc] init]); | |
69 | } | |
70 | ||
71 | #if FOUNDATION | |
72 | { | |
73 | static bool warned; | |
74 | if (!warned) testwarn("rdar://7138159 NSAutoreleasePool leaks"); | |
75 | warned = true; | |
76 | } | |
77 | state += NESTED_COUNT; | |
78 | #else | |
79 | // local pool, not popped | |
80 | RR_PUSH(); | |
81 | for (int i = 0; i < NESTED_COUNT; i++) { | |
82 | RR_AUTORELEASE([[Deallocator alloc] init]); | |
83 | } | |
84 | #endif | |
85 | ||
86 | [super dealloc]; | |
87 | } | |
88 | @end | |
89 | ||
90 | void *autorelease_lots_fn(void *singlePool) | |
91 | { | |
92 | // Enough to blow out the stack if AutoreleasePoolPage is recursive. | |
93 | const int COUNT = 1024*1024; | |
94 | state = 0; | |
95 | ||
96 | int p = 0; | |
97 | void **pools = (void**)malloc((COUNT+1) * sizeof(void*)); | |
98 | pools[p++] = RR_PUSH(); | |
99 | ||
100 | id obj = RR_AUTORELEASE([[Deallocator alloc] init]); | |
101 | ||
102 | // last pool has only 1 autorelease in it | |
103 | pools[p++] = RR_PUSH(); | |
104 | ||
105 | for (int i = 0; i < COUNT; i++) { | |
106 | if (rand() % 1000 == 0 && !singlePool) { | |
107 | pools[p++] = RR_PUSH(); | |
108 | } else { | |
109 | RR_AUTORELEASE(RR_RETAIN(obj)); | |
110 | } | |
111 | } | |
112 | ||
113 | testassert(state == 0); | |
114 | while (--p) { | |
115 | RR_POP(pools[p]); | |
116 | } | |
117 | testassert(state == 0); | |
118 | testassert(RR_RETAINCOUNT(obj) == 1); | |
119 | RR_POP(pools[0]); | |
120 | testassert(state == 1); | |
121 | free(pools); | |
122 | ||
123 | return NULL; | |
124 | } | |
125 | ||
126 | void *nsthread_fn(void *arg __unused) | |
127 | { | |
128 | [NSThread currentThread]; | |
129 | void *pool = RR_PUSH(); | |
130 | RR_AUTORELEASE([[Deallocator alloc] init]); | |
131 | RR_POP(pool); | |
132 | return NULL; | |
133 | } | |
134 | ||
135 | void cycle(void) | |
136 | { | |
137 | // Normal autorelease. | |
138 | testprintf("-- Normal autorelease.\n"); | |
139 | { | |
140 | void *pool = RR_PUSH(); | |
141 | state = 0; | |
142 | RR_AUTORELEASE([[Deallocator alloc] init]); | |
143 | testassert(state == 0); | |
144 | RR_POP(pool); | |
145 | testassert(state == 1); | |
146 | } | |
147 | ||
148 | // Autorelease during dealloc during autoreleasepool-pop. | |
149 | // That autorelease is handled by the popping pool, not the one above it. | |
150 | testprintf("-- Autorelease during dealloc during autoreleasepool-pop.\n"); | |
151 | { | |
152 | void *pool = RR_PUSH(); | |
153 | state = 0; | |
154 | RR_AUTORELEASE([[AutoreleaseDuringDealloc alloc] init]); | |
155 | testassert(state == 0); | |
156 | RR_POP(pool); | |
157 | testassert(state == 2); | |
158 | } | |
159 | ||
160 | // Autorelease pool during dealloc during autoreleasepool-pop. | |
161 | testprintf("-- Autorelease pool during dealloc during autoreleasepool-pop.\n"); | |
162 | { | |
163 | void *pool = RR_PUSH(); | |
164 | state = 0; | |
165 | RR_AUTORELEASE([[AutoreleasePoolDuringDealloc alloc] init]); | |
166 | testassert(state == 0); | |
167 | RR_POP(pool); | |
168 | testassert(state == 4 * NESTED_COUNT); | |
169 | } | |
170 | ||
171 | // Top-level thread pool popped normally. | |
172 | // Check twice - once for empty placeholder, once without. | |
173 | # if DEBUG_POOL_ALLOCATION || FOUNDATION | |
174 | // DebugPoolAllocation disables the empty placeholder pool. | |
175 | // Guard Malloc disables the empty placeholder pool (checked at runtime) | |
176 | // Foundation makes RR_PUSH return an NSAutoreleasePool not the raw token. | |
177 | # define CHECK_PLACEHOLDER 0 | |
178 | # else | |
179 | # define CHECK_PLACEHOLDER 1 | |
180 | # endif | |
181 | testprintf("-- Thread-level pool popped normally.\n"); | |
182 | { | |
183 | state = 0; | |
184 | testonthread(^{ | |
185 | void *pool = RR_PUSH(); | |
186 | #if CHECK_PLACEHOLDER | |
187 | if (!is_guardmalloc()) { | |
188 | testassert(pool == (void*)1); | |
189 | } | |
190 | #endif | |
191 | RR_AUTORELEASE([[Deallocator alloc] init]); | |
192 | RR_POP(pool); | |
193 | pool = RR_PUSH(); | |
194 | #if CHECK_PLACEHOLDER | |
195 | if (!is_guardmalloc()) { | |
196 | testassert(pool != (void*)1); | |
197 | } | |
198 | #endif | |
199 | RR_AUTORELEASE([[Deallocator alloc] init]); | |
200 | RR_POP(pool); | |
201 | }); | |
202 | testassert(state == 2); | |
203 | } | |
204 | ||
205 | ||
206 | // Autorelease with no pool. | |
207 | testprintf("-- Autorelease with no pool.\n"); | |
208 | { | |
209 | state = 0; | |
210 | testonthread(^{ | |
211 | RR_AUTORELEASE([[Deallocator alloc] init]); | |
212 | }); | |
213 | testassert(state == 1); | |
214 | } | |
215 | ||
216 | // Autorelease with no pool after popping the top-level pool. | |
217 | testprintf("-- Autorelease with no pool after popping the last pool.\n"); | |
218 | { | |
219 | state = 0; | |
220 | testonthread(^{ | |
221 | void *pool = RR_PUSH(); | |
222 | RR_AUTORELEASE([[Deallocator alloc] init]); | |
223 | RR_POP(pool); | |
224 | RR_AUTORELEASE([[Deallocator alloc] init]); | |
225 | }); | |
226 | testassert(state == 2); | |
227 | } | |
228 | ||
229 | // Top-level thread pool not popped. | |
230 | // The runtime should clean it up. | |
231 | #if FOUNDATION | |
232 | { | |
233 | static bool warned; | |
234 | if (!warned) testwarn("rdar://7138159 NSAutoreleasePool leaks"); | |
235 | warned = true; | |
236 | } | |
237 | #else | |
238 | testprintf("-- Thread-level pool not popped.\n"); | |
239 | { | |
240 | state = 0; | |
241 | testonthread(^{ | |
242 | RR_PUSH(); | |
243 | RR_AUTORELEASE([[Deallocator alloc] init]); | |
244 | // pool not popped | |
245 | }); | |
246 | testassert(state == 1); | |
247 | } | |
248 | #endif | |
249 | ||
250 | // Intermediate pool not popped. | |
251 | // Popping the containing pool should clean up the skipped pool first. | |
252 | #if FOUNDATION | |
253 | { | |
254 | static bool warned; | |
255 | if (!warned) testwarn("rdar://7138159 NSAutoreleasePool leaks"); | |
256 | warned = true; | |
257 | } | |
258 | #else | |
259 | testprintf("-- Intermediate pool not popped.\n"); | |
260 | { | |
261 | void *pool = RR_PUSH(); | |
262 | void *pool2 = RR_PUSH(); | |
263 | RR_AUTORELEASE([[Deallocator alloc] init]); | |
264 | state = 0; | |
265 | (void)pool2; // pool2 not popped | |
266 | RR_POP(pool); | |
267 | testassert(state == 1); | |
268 | } | |
269 | #endif | |
270 | } | |
271 | ||
272 | ||
273 | static void | |
274 | slow_cycle(void) | |
275 | { | |
276 | // Large autorelease stack. | |
277 | // Do this only once because it's slow. | |
278 | testprintf("-- Large autorelease stack.\n"); | |
279 | { | |
280 | // limit stack size: autorelease pop should not be recursive | |
281 | pthread_t th; | |
282 | pthread_create(&th, &smallstack, &autorelease_lots_fn, NULL); | |
283 | pthread_join(th, NULL); | |
284 | } | |
285 | ||
286 | // Single large autorelease pool. | |
287 | // Do this only once because it's slow. | |
288 | testprintf("-- Large autorelease pool.\n"); | |
289 | { | |
290 | // limit stack size: autorelease pop should not be recursive | |
291 | pthread_t th; | |
292 | pthread_create(&th, &smallstack, &autorelease_lots_fn, (void*)1); | |
293 | pthread_join(th, NULL); | |
294 | } | |
295 | } | |
296 | ||
297 | ||
298 | int main() | |
299 | { | |
300 | pthread_attr_init(&smallstack); | |
301 | pthread_attr_setstacksize(&smallstack, 32768); | |
302 | ||
303 | // inflate the refcount side table so it doesn't show up in leak checks | |
304 | { | |
305 | int count = 10000; | |
306 | id *objs = (id *)malloc(count*sizeof(id)); | |
307 | for (int i = 0; i < count; i++) { | |
308 | objs[i] = RR_RETAIN([NSObject new]); | |
309 | } | |
310 | for (int i = 0; i < count; i++) { | |
311 | RR_RELEASE(objs[i]); | |
312 | RR_RELEASE(objs[i]); | |
313 | } | |
314 | free(objs); | |
315 | } | |
316 | ||
317 | #if FOUNDATION | |
318 | // inflate NSAutoreleasePool's instance cache | |
319 | { | |
320 | int count = 32; | |
321 | id *objs = (id *)malloc(count * sizeof(id)); | |
322 | for (int i = 0; i < count; i++) { | |
323 | objs[i] = [[NSAutoreleasePool alloc] init]; | |
324 | } | |
325 | for (int i = 0; i < count; i++) { | |
326 | [objs[count-i-1] release]; | |
327 | } | |
328 | ||
329 | free(objs); | |
330 | } | |
331 | #endif | |
332 | ||
333 | // preheat | |
334 | { | |
335 | for (int i = 0; i < 100; i++) { | |
336 | cycle(); | |
337 | } | |
338 | ||
339 | slow_cycle(); | |
340 | } | |
341 | ||
342 | // check for leaks using top-level pools | |
343 | { | |
344 | leak_mark(); | |
345 | ||
346 | for (int i = 0; i < 1000; i++) { | |
347 | cycle(); | |
348 | } | |
349 | ||
350 | leak_check(0); | |
351 | ||
352 | slow_cycle(); | |
353 | ||
354 | leak_check(0); | |
355 | } | |
356 | ||
357 | // check for leaks using pools not at top level | |
358 | // fixme for FOUNDATION this leak mark/check needs | |
359 | // to be outside the autorelease pool for some reason | |
360 | leak_mark(); | |
361 | void *pool = RR_PUSH(); | |
362 | { | |
363 | for (int i = 0; i < 1000; i++) { | |
364 | cycle(); | |
365 | } | |
366 | ||
367 | slow_cycle(); | |
368 | } | |
369 | RR_POP(pool); | |
370 | leak_check(0); | |
371 | ||
372 | // NSThread. | |
373 | // Can't leak check this because it's too noisy. | |
374 | testprintf("-- NSThread.\n"); | |
375 | { | |
376 | pthread_t th; | |
377 | pthread_create(&th, &smallstack, &nsthread_fn, 0); | |
378 | pthread_join(th, NULL); | |
379 | } | |
380 | ||
381 | // NO LEAK CHECK HERE | |
382 | ||
383 | succeed(NAME); | |
384 | } |