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