1/*
2need exception-safe ARC for exception deallocation tests
3need F/CF for testonthread() in GC mode
4TEST_CFLAGS -fobjc-arc-exceptions -framework Foundation
5
6llvm-gcc unavoidably warns about our deliberately out-of-order handlers
7
8TEST_BUILD_OUTPUT
9.*exc.m: In function .*
10.*exc.m:\d+: warning: exception of type .* will be caught
11.*exc.m:\d+: warning:    by earlier handler for .*
12.*exc.m:\d+: warning: exception of type .* will be caught
13.*exc.m:\d+: warning:    by earlier handler for .*
14.*exc.m:\d+: warning: exception of type .* will be caught
15.*exc.m:\d+: warning:    by earlier handler for .*
16OR
17END
18*/
19
20#include "test.h"
21#include "testroot.i"
22#include <objc/runtime.h>
23#include <objc/objc-exception.h>
24
25static volatile int state = 0;
26static volatile int dealloced = 0;
27#define BAD 1000000
28
29#if defined(USE_FOUNDATION)
30
31#include <Foundation/Foundation.h>
32
33@interface Super : NSException @end
34@implementation Super
35+(id)exception { return AUTORELEASE([[self alloc] initWithName:@"Super" reason:@"reason" userInfo:nil]);  }
36-(void)check { state++; }
37+(void)check { testassert(!"caught class object, not instance"); }
38-(void)dealloc { dealloced++; SUPER_DEALLOC(); }
39-(void)finalize { dealloced++; [super finalize]; }
40@end
41
42#define FILENAME "nsexc.m"
43
44#else
45
46@interface Super : TestRoot @end
47@implementation Super
48+(id)exception { return AUTORELEASE([self new]); }
49-(void)check { state++; }
50+(void)check { testassert(!"caught class object, not instance"); }
51-(void)dealloc { dealloced++; SUPER_DEALLOC(); }
52-(void)finalize { dealloced++; [super finalize]; }
53@end
54
55#define FILENAME "exc.m"
56
57#endif
58
59@interface Sub : Super @end
60@implementation Sub
61@end
62
63
64#if __OBJC2__  &&  !TARGET_OS_EMBEDDED  &&  !TARGET_OS_IPHONE
65void altHandlerFail(id unused __unused, void *context __unused)
66{
67    fail("altHandlerFail called");
68}
69
70#define ALT_HANDLER(n)                                          \
71    void altHandler##n(id unused __unused, void *context)       \
72    {                                                           \
73        testassert(context == (void*)&altHandler##n);           \
74        testassert(state == n);                                 \
75        state++;                                                \
76    }
77
78ALT_HANDLER(1)
79ALT_HANDLER(2)
80ALT_HANDLER(3)
81ALT_HANDLER(4)
82ALT_HANDLER(5)
83ALT_HANDLER(6)
84ALT_HANDLER(7)
85
86
87static void throwWithAltHandler(void) __attribute__((noinline));
88static void throwWithAltHandler(void)
89{
90    @try {
91        state++;
92        uintptr_t token = objc_addExceptionHandler(altHandler3, (void*)altHandler3);
93        // state++ inside alt handler
94        @throw [Super exception];
95        state = BAD;
96        objc_removeExceptionHandler(token);
97    }
98    @catch (Sub *e) {
99        state = BAD;
100    }
101    state = BAD;
102}
103
104
105static void throwWithAltHandlerAndRethrow(void) __attribute__((noinline));
106static void throwWithAltHandlerAndRethrow(void)
107{
108    @try {
109        state++;
110        uintptr_t token = objc_addExceptionHandler(altHandler3, (void*)altHandler3);
111        // state++ inside alt handler
112        @throw [Super exception];
113        state = BAD;
114        objc_removeExceptionHandler(token);
115    }
116    @catch (...) {
117        testassert(state == 4);
118        state++;
119        @throw;
120    }
121    state = BAD;
122}
123
124#endif
125
126#if __cplusplus  &&  __OBJC2__
127#include <exception>
128void terminator() {
129    succeed(FILENAME);
130}
131#endif
132
133
134#define TEST(code)                                              \
135    do {                                                        \
136        testonthread(^{ PUSH_POOL { code } POP_POOL; });        \
137        testcollect();                                          \
138    } while (0)
139
140
141
142int main()
143{
144    testprintf("try-catch-finally, exception caught exactly\n");
145
146    TEST({
147        state = 0;
148        dealloced = 0;
149        @try {
150            state++;
151            @try {
152                state++;
153                @throw [Super exception];
154                state = BAD;
155            }
156            @catch (Super *e) {
157                state++;
158                [e check];  // state++
159            }
160            @finally {
161                state++;
162            }
163            state++;
164        }
165        @catch (...) {
166            state = BAD;
167        }
168    });
169    testassert(state == 6);
170    testassert(dealloced == 1);
171
172
173    testprintf("try-finally, no exception thrown\n");
174
175    TEST({
176        state = 0;
177        dealloced = 0;
178        @try {
179            state++;
180            @try {
181                state++;
182            }
183            @finally {
184                state++;
185            }
186            state++;
187        }
188        @catch (...) {
189            state = BAD;
190        }
191    });
192    testassert(state == 4);
193    testassert(dealloced == 0);
194
195
196    testprintf("try-finally, with exception\n");
197
198    TEST({
199        state = 0;
200        dealloced = 0;
201        @try {
202            state++;
203            @try {
204                state++;
205                @throw [Super exception];
206                state = BAD;
207            }
208            @finally {
209                state++;
210            }
211            state = BAD;
212        }
213        @catch (id e) {
214            state++;
215            [e check];  // state++
216        }
217    });
218    testassert(state == 5);
219    testassert(dealloced == 1);
220
221
222#if __OBJC2__
223    testprintf("try-finally, with autorelease pool pop during unwind\n");
224    // Popping an autorelease pool during unwind used to deallocate the
225    // exception object, but now we retain them while in flight.
226
227    // This use-after-free is undetected without MallocScribble or guardmalloc.
228    if (!getenv("MallocScribble")  &&
229        (!getenv("DYLD_INSERT_LIBRARIES")  ||
230         !strstr(getenv("DYLD_INSERT_LIBRARIES"), "libgmalloc")))
231    {
232        testwarn("MallocScribble not set");
233    }
234
235    TEST({
236        state = 0;
237        dealloced = 0;
238        @try {
239            void *pool2 = objc_autoreleasePoolPush();
240            state++;
241            @try {
242                state++;
243                @throw [Super exception];
244                state = BAD;
245            }
246            @finally {
247                state++;
248                objc_autoreleasePoolPop(pool2);
249            }
250            state = BAD;
251        }
252        @catch (id e) {
253            state++;
254            [e check];  // state++
255        }
256    });
257    testassert(state == 5);
258    testassert(dealloced == 1);
259#endif
260
261
262    testprintf("try-catch-finally, no exception\n");
263
264    TEST({
265        state = 0;
266        dealloced = 0;
267        @try {
268            state++;
269            @try {
270                state++;
271            }
272            @catch (...) {
273                state = BAD;
274            }
275            @finally {
276                state++;
277            }
278            state++;
279        } @catch (...) {
280            state = BAD;
281        }
282    });
283    testassert(state == 4);
284    testassert(dealloced == 0);
285
286
287    testprintf("try-catch-finally, exception not caught\n");
288
289    TEST({
290        state = 0;
291        dealloced = 0;
292        @try {
293            state++;
294            @try {
295                state++;
296                @throw [Super exception];
297                state = BAD;
298            }
299            @catch (Sub *e) {
300                state = BAD;
301            }
302            @finally {
303                state++;
304            }
305            state = BAD;
306        }
307        @catch (id e) {
308            state++;
309            [e check];  // state++
310        }
311    });
312    testassert(state == 5);
313    testassert(dealloced == 1);
314
315
316    testprintf("try-catch-finally, exception caught exactly, rethrown\n");
317
318    TEST({
319        state = 0;
320        dealloced = 0;
321        @try {
322            state++;
323            @try {
324                state++;
325                @throw [Super exception];
326                state = BAD;
327            }
328            @catch (Super *e) {
329                state++;
330                [e check];  // state++
331                @throw;
332                state = BAD;
333            }
334            @finally {
335                state++;
336            }
337            state = BAD;
338        }
339        @catch (id e) {
340            state++;
341            [e check];  // state++
342        }
343    });
344    testassert(state == 7);
345    testassert(dealloced == 1);
346
347
348    testprintf("try-catch, no exception\n");
349
350    TEST({
351        state = 0;
352        dealloced = 0;
353        @try {
354            state++;
355            @try {
356                state++;
357            }
358            @catch (...) {
359                state = BAD;
360            }
361            state++;
362        } @catch (...) {
363            state = BAD;
364        }
365    });
366    testassert(state == 3);
367    testassert(dealloced == 0);
368
369
370    testprintf("try-catch, exception not caught\n");
371
372    TEST({
373        state = 0;
374        dealloced = 0;
375        @try {
376            state++;
377            @try {
378                state++;
379                @throw [Super exception];
380                state = BAD;
381            }
382            @catch (Sub *e) {
383                state = BAD;
384            }
385            state = BAD;
386        }
387        @catch (id e) {
388            state++;
389            [e check];  // state++
390        }
391    });
392    testassert(state == 4);
393    testassert(dealloced == 1);
394
395
396    testprintf("try-catch, exception caught exactly\n");
397
398    TEST({
399        state = 0;
400        dealloced = 0;
401        @try {
402            state++;
403            @try {
404                state++;
405                @throw [Super exception];
406                state = BAD;
407            }
408            @catch (Super *e) {
409                state++;
410                [e check];  // state++
411            }
412            state++;
413        }
414        @catch (...) {
415            state = BAD;
416        }
417    });
418    testassert(state == 5);
419    testassert(dealloced == 1);
420
421
422    testprintf("try-catch, exception caught exactly, rethrown\n");
423
424    TEST({
425        state = 0;
426        dealloced = 0;
427        @try {
428            state++;
429            @try {
430                state++;
431                @throw [Super exception];
432                state = BAD;
433            }
434            @catch (Super *e) {
435                state++;
436                [e check];  // state++
437                @throw;
438                state = BAD;
439            }
440            state = BAD;
441        }
442        @catch (id e) {
443            state++;
444            [e check];  // state++
445        }
446    });
447    testassert(state == 6);
448    testassert(dealloced == 1);
449
450
451    testprintf("try-catch, exception caught exactly, thrown again explicitly\n");
452
453    TEST({
454        state = 0;
455        dealloced = 0;
456        @try {
457            state++;
458            @try {
459                state++;
460                @throw [Super exception];
461                state = BAD;
462            }
463            @catch (Super *e) {
464                state++;
465                [e check];  // state++
466                @throw e;
467                state = BAD;
468            }
469            state = BAD;
470        }
471        @catch (id e) {
472            state++;
473            [e check];  // state++
474        }
475    });
476    testassert(state == 6);
477    testassert(dealloced == 1);
478
479
480    testprintf("try-catch, default catch, rethrown\n");
481
482    TEST({
483        state = 0;
484        dealloced = 0;
485        @try {
486            state++;
487            @try {
488                state++;
489                @throw [Super exception];
490                state = BAD;
491            }
492            @catch (...) {
493                state++;
494                @throw;
495                state = BAD;
496            }
497            state = BAD;
498        }
499        @catch (id e) {
500            state++;
501            [e check];  // state++
502        }
503    });
504    testassert(state == 5);
505    testassert(dealloced == 1);
506
507
508    testprintf("try-catch, default catch, rethrown and caught inside nested handler\n");
509
510    TEST({
511        state = 0;
512        dealloced = 0;
513        @try {
514            state++;
515            @try {
516                state++;
517                @throw [Super exception];
518                state = BAD;
519            }
520            @catch (...) {
521                state++;
522
523                @try {
524                    state++;
525                    @throw;
526                    state = BAD;
527                } @catch (Sub *e) {
528                    state = BAD;
529                } @catch (Super *e) {
530                    state++;
531                    [e check];  // state++
532                } @catch (...) {
533                    state = BAD;
534                } @finally {
535                    state++;
536                }
537
538                state++;
539            }
540            state++;
541        }
542        @catch (...) {
543            state = BAD;
544        }
545    });
546    testassert(state == 9);
547    testassert(dealloced == 1);
548
549
550    testprintf("try-catch, default catch, rethrown inside nested handler but not caught\n");
551
552    TEST({
553        state = 0;
554        dealloced = 0;
555        @try {
556            state++;
557            @try {
558                state++;
559                @throw [Super exception];
560                state = BAD;
561            }
562            @catch (...) {
563                state++;
564
565                @try {
566                    state++;
567                    @throw;
568                    state = BAD;
569                }
570                @catch (Sub *e) {
571                    state = BAD;
572                }
573                @finally {
574                    state++;
575                }
576
577                state = BAD;
578            }
579            state = BAD;
580        }
581        @catch (id e) {
582            state++;
583            [e check];  // state++
584        }
585    });
586    testassert(state == 7);
587    testassert(dealloced == 1);
588
589
590#if __cplusplus  &&  __OBJC2__
591    testprintf("C++ try/catch, Objective-C exception superclass\n");
592
593    TEST({
594        state = 0;
595        dealloced = 0;
596        try {
597            state++;
598            try {
599                state++;
600                try {
601                    state++;
602                    @throw [Super exception];
603                    state = BAD;
604                } catch (...) {
605                    state++;
606                    throw;
607                    state = BAD;
608                }
609                state = BAD;
610            } catch (void *e) {
611                state = BAD;
612            } catch (int e) {
613                state = BAD;
614            } catch (Sub *e) {
615                state = BAD;
616            } catch (Super *e) {
617                state++;
618                [e check];  // state++
619                throw;
620            } catch (...) {
621                state = BAD;
622            }
623        } catch (id e) {
624            state++;
625            [e check];  // state++;
626        }
627    });
628    testassert(state == 8);
629    testassert(dealloced == 1);
630
631
632    testprintf("C++ try/catch, Objective-C exception subclass\n");
633
634    TEST({
635        state = 0;
636        dealloced = 0;
637        try {
638            state++;
639            try {
640                state++;
641                try {
642                    state++;
643                    @throw [Sub exception];
644                    state = BAD;
645                } catch (...) {
646                    state++;
647                    throw;
648                    state = BAD;
649                }
650                state = BAD;
651            } catch (void *e) {
652                state = BAD;
653            } catch (int e) {
654                state = BAD;
655            } catch (Super *e) {
656                state++;
657                [e check];  // state++
658                throw;
659            } catch (Sub *e) {
660                state = BAD;
661            } catch (...) {
662                state = BAD;
663            }
664        } catch (id e) {
665            state++;
666            [e check];  // state++;
667        }
668    });
669    testassert(state == 8);
670    testassert(dealloced == 1);
671
672#endif
673
674
675#if !__OBJC2__  ||  TARGET_OS_EMBEDDED  ||  TARGET_OS_IPHONE
676        // alt handlers for modern Mac OS only
677
678#else
679    {
680        // alt handlers
681        // run a lot to catch failed unregistration (runtime complains at 1000)
682#define ALT_HANDLER_REPEAT 2000
683
684        testprintf("alt handler, no exception\n");
685
686        TEST({
687            dealloced = 0;
688            for (int i = 0; i < ALT_HANDLER_REPEAT; i++) {
689                state = 0;
690                @try {
691                    state++;
692                    @try {
693                        uintptr_t token = objc_addExceptionHandler(altHandlerFail, 0);
694                        state++;
695                        objc_removeExceptionHandler(token);
696                    }
697                    @catch (...) {
698                        state = BAD;
699                    }
700                    state++;
701                } @catch (...) {
702                    state = BAD;
703                }
704                testassert(state == 3);
705            }
706        });
707        testassert(dealloced == 0);
708
709
710        testprintf("alt handler, exception thrown through\n");
711
712        TEST({
713            dealloced = 0;
714            for (int i = 0; i < ALT_HANDLER_REPEAT; i++) {
715                state = 0;
716                @try {
717                    state++;
718                    @try {
719                        state++;
720                        uintptr_t token = objc_addExceptionHandler(altHandler2, (void*)altHandler2);
721                        // state++ inside alt handler
722                        @throw [Super exception];
723                        state = BAD;
724                        objc_removeExceptionHandler(token);
725                    }
726                    @catch (Sub *e) {
727                        state = BAD;
728                    }
729                    state = BAD;
730                }
731                @catch (id e) {
732                    testassert(state == 3);
733                    state++;
734                    [e check];  // state++
735                }
736                testassert(state == 5);
737            }
738        });
739        testassert(dealloced == ALT_HANDLER_REPEAT);
740
741
742        testprintf("alt handler, nested\n");
743
744        TEST({
745            dealloced = 0;
746            for (int i = 0; i < ALT_HANDLER_REPEAT; i++) {
747                state = 0;
748                @try {
749                    state++;
750                    @try {
751                        state++;
752                        // same-level handlers called in FIFO order (not stack-like)
753                        uintptr_t token = objc_addExceptionHandler(altHandler4, (void*)altHandler4);
754                        // state++ inside alt handler
755                        uintptr_t token2 = objc_addExceptionHandler(altHandler5, (void*)altHandler5);
756                        // state++ inside alt handler
757                        throwWithAltHandler();  // state += 2 inside
758                        state = BAD;
759                        objc_removeExceptionHandler(token);
760                        objc_removeExceptionHandler(token2);
761                    }
762                    @catch (id e) {
763                        testassert(state == 6);
764                        state++;
765                        [e check];  // state++;
766                    }
767                    state++;
768                }
769                @catch (...) {
770                    state = BAD;
771                }
772                testassert(state == 9);
773            }
774        });
775        testassert(dealloced == ALT_HANDLER_REPEAT);
776
777
778        testprintf("alt handler, nested, rethrows in between\n");
779
780        TEST({
781            dealloced = 0;
782            for (int i = 0; i < ALT_HANDLER_REPEAT; i++) {
783                state = 0;
784                @try {
785                    state++;
786                    @try {
787                        state++;
788                        // same-level handlers called in FIFO order (not stack-like)
789                        uintptr_t token = objc_addExceptionHandler(altHandler5, (void*)altHandler5);
790                        // state++ inside alt handler
791                        uintptr_t token2 = objc_addExceptionHandler(altHandler6, (void*)altHandler6);
792                        // state++ inside alt handler
793                        throwWithAltHandlerAndRethrow();  // state += 3 inside
794                        state = BAD;
795                        objc_removeExceptionHandler(token);
796                        objc_removeExceptionHandler(token2);
797                    }
798                    @catch (...) {
799                        testassert(state == 7);
800                        state++;
801                        @throw;
802                    }
803                    state = BAD;
804                }
805                @catch (id e) {
806                    testassert(state == 8);
807                    state++;
808                    [e check];  // state++
809                }
810                testassert(state == 10);
811            }
812        });
813        testassert(dealloced == ALT_HANDLER_REPEAT);
814
815
816        testprintf("alt handler, exception thrown and caught inside\n");
817
818        TEST({
819            dealloced = 0;
820            for (int i = 0; i < ALT_HANDLER_REPEAT; i++) {
821                state = 0;
822                @try {
823                    state++;
824                    uintptr_t token = objc_addExceptionHandler(altHandlerFail, 0);
825                    @try {
826                        state++;
827                        @throw [Super exception];
828                        state = BAD;
829                    }
830                    @catch (Super *e) {
831                        state++;
832                        [e check];  // state++
833                    }
834                    state++;
835                    objc_removeExceptionHandler(token);
836                }
837                @catch (...) {
838                    state = BAD;
839                }
840                testassert(state == 5);
841            }
842        });
843        testassert(dealloced == ALT_HANDLER_REPEAT);
844
845
846#if defined(USE_FOUNDATION)
847        testprintf("alt handler, rdar://10055775\n");
848
849        TEST({
850            dealloced = 0;
851            for (int i = 0; i < ALT_HANDLER_REPEAT; i++) {
852                state = 0;
853                @try {
854                    uintptr_t token = objc_addExceptionHandler(altHandler1, (void*)altHandler1);
855                    {
856                        id x = [NSArray array];
857                        x = [NSArray array];
858                    }
859                    state++;
860                    // state++ inside alt handler
861                    [Super raise:@"foo" format:@"bar"];
862                    state = BAD;
863                    objc_removeExceptionHandler(token);
864                } @catch (id e) {
865                    state++;
866                    testassert(state == 3);
867                }
868                testassert(state == 3);
869            }
870        });
871        testassert(dealloced == ALT_HANDLER_REPEAT);
872
873// defined(USE_FOUNDATION)
874#endif
875
876    }
877// alt handlers
878#endif
879
880#if __cplusplus  &&  __OBJC2__
881    std::set_terminate(terminator);
882    objc_terminate();
883    fail("should not have returned from objc_terminate()");
884#else
885    succeed(FILENAME);
886#endif
887}
888
889