// RUN: %clang_cc1 -verify -fsyntax-only -fblocks -fobjc-exceptions -Wcompletion-handler %s #define NULL (void *)0 #define nil (id)0 #define CALLED_ONCE __attribute__((called_once)) #define NORETURN __attribute__((noreturn)) @protocol NSObject @end @interface NSObject - (id)copy; - (id)class; - autorelease; @end typedef unsigned int NSUInteger; typedef struct { } NSFastEnumerationState; @interface NSArray <__covariant NSFastEnumeration> - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len; @end @interface NSMutableArray : NSArray - addObject:anObject; @end @class NSString, Protocol; extern void NSLog(NSString *format, ...); void escape(void (^callback)(void)); void escape_void(void *); void indirect_call(void (^callback)(void) CALLED_ONCE); void indirect_conv(void (^completionHandler)(void)); void filler(void); void exit(int) NORETURN; void double_call_one_block(void (^callback)(void) CALLED_ONCE) { callback(); // expected-note{{previous call is here}} callback(); // expected-warning{{'callback' parameter marked 'called_once' is called twice}} } void double_call_one_block_parens(void (^callback)(void) CALLED_ONCE) { (callback)(); // expected-note{{previous call is here}} (callback)(); // expected-warning{{'callback' parameter marked 'called_once' is called twice}} } void double_call_one_block_ptr(void (*callback)(void) CALLED_ONCE) { callback(); // expected-note{{previous call is here}} callback(); // expected-warning{{'callback' parameter marked 'called_once' is called twice}} } void double_call_one_block_ptr_deref(void (*callback)(void) CALLED_ONCE) { (*callback)(); // expected-note{{previous call is here}} (*callback)(); // expected-warning{{'callback' parameter marked 'called_once' is called twice}} } void multiple_call_one_block(void (^callback)(void) CALLED_ONCE) { // We don't really need to repeat the same warning for the same parameter. callback(); // no-warning callback(); // no-warning callback(); // no-warning callback(); // expected-note{{previous call is here}} callback(); // expected-warning{{'callback' parameter marked 'called_once' is called twice}} } void double_call_branching_1(int cond, void (^callback)(void) CALLED_ONCE) { if (cond) { callback(); // expected-note{{previous call is here}} } else { cond += 42; } callback(); // expected-warning{{'callback' parameter marked 'called_once' is called twice}} } void double_call_branching_2(int cond, void (^callback)(void) CALLED_ONCE) { callback(); // expected-note@-1{{previous call is here; set to nil to indicate it cannot be called afterwards}} if (cond) { callback(); // expected-warning{{'callback' parameter marked 'called_once' is called twice}} } else { cond += 42; } } void double_call_branching_3(int cond, void (^callback)(void) CALLED_ONCE) { if (cond) { callback(); } else { callback(); } // no-warning } void double_call_branching_4(int cond1, int cond2, void (^callback)(void) CALLED_ONCE) { if (cond1) { cond2 = !cond2; } else { callback(); // expected-note@-1{{previous call is here; set to nil to indicate it cannot be called afterwards}} } if (cond2) { callback(); // expected-warning{{'callback' parameter marked 'called_once' is called twice}} } } void double_call_loop(int counter, void (^callback)(void) CALLED_ONCE) { while (counter > 0) { counter--; // Both note and warning are on the same line, which is a common situation // in loops. callback(); // expected-note{{previous call is here}} // expected-warning@-1{{'callback' parameter marked 'called_once' is called twice}} } } void never_called_trivial(void (^callback)(void) CALLED_ONCE) { // expected-warning@-1{{'callback' parameter marked 'called_once' is never called}} } int never_called_branching(int x, void (^callback)(void) CALLED_ONCE) { // expected-warning@-1{{'callback' parameter marked 'called_once' is never called}} x -= 42; if (x == 10) { return 0; } return x + 15; } void escaped_one_block_1(void (^callback)(void) CALLED_ONCE) { escape(callback); // no-warning } void escaped_one_block_2(void (^callback)(void) CALLED_ONCE) { escape(callback); // no-warning callback(); } void escaped_one_path_1(int cond, void (^callback)(void) CALLED_ONCE) { if (cond) { escape(callback); // no-warning } else { callback(); } } void escaped_one_path_2(int cond, void (^callback)(void) CALLED_ONCE) { if (cond) { escape(callback); // no-warning } callback(); } void escaped_one_path_3(int cond, void (^callback)(void) CALLED_ONCE) { if (cond) { // expected-warning@-1{{'callback' parameter marked 'called_once' is never used when taking false branch}} escape(callback); } } void escape_in_between_1(void (^callback)(void) CALLED_ONCE) { callback(); // expected-note{{previous call is here}} escape(callback); callback(); // expected-warning{{'callback' parameter marked 'called_once' is called twice}} } void escape_in_between_2(int cond, void (^callback)(void) CALLED_ONCE) { callback(); // expected-note{{previous call is here}} if (cond) { escape(callback); } callback(); // expected-warning{{'callback' parameter marked 'called_once' is called twice}} } void escape_in_between_3(int cond, void (^callback)(void) CALLED_ONCE) { callback(); // expected-note{{previous call is here}} if (cond) { escape(callback); } else { escape_void((__bridge void *)callback); } callback(); // expected-warning{{'callback' parameter marked 'called_once' is called twice}} } void escaped_as_void_ptr(void (^callback)(void) CALLED_ONCE) { escape_void((__bridge void *)callback); // no-warning } void indirect_call_no_warning_1(void (^callback)(void) CALLED_ONCE) { indirect_call(callback); // no-warning } void indirect_call_no_warning_2(int cond, void (^callback)(void) CALLED_ONCE) { if (cond) { indirect_call(callback); } else { callback(); } // no-warning } void indirect_call_double_call(void (^callback)(void) CALLED_ONCE) { indirect_call(callback); // expected-note{{previous call is here}} callback(); // expected-warning{{'callback' parameter marked 'called_once' is called twice}} } void indirect_call_within_direct_call(void (^callback)(void) CALLED_ONCE, void (^meta)(void (^param)(void) CALLED_ONCE) CALLED_ONCE) { // TODO: Report warning for 'callback'. // At the moment, it is not possible to access 'called_once' attribute from the type // alone when there is no actual declaration of the marked parameter. meta(callback); callback(); // no-warning } void block_call_1(void (^callback)(void) CALLED_ONCE) { indirect_call(^{ callback(); }); callback(); // no-warning } void block_call_2(void (^callback)(void) CALLED_ONCE) { escape(^{ callback(); }); callback(); // no-warning } void block_call_3(int cond, void (^callback)(void) CALLED_ONCE) { ^{ if (cond) { callback(); // expected-note{{previous call is here}} } callback(); // expected-warning{{'callback' parameter marked 'called_once' is called twice}} }(); // no-warning } void block_call_4(int cond, void (^callback)(void) CALLED_ONCE) { ^{ if (cond) { // expected-warning@-1{{'callback' parameter marked 'called_once' is never used when taking false branch}} escape(callback); } }(); } void block_call_5(void (^outer)(void) CALLED_ONCE) { ^(void (^inner)(void) CALLED_ONCE) { // expected-warning@-1{{'inner' parameter marked 'called_once' is never called}} }(outer); } void block_with_called_once(void (^outer)(void) CALLED_ONCE) { escape_void((__bridge void *)^(void (^inner)(void) CALLED_ONCE) { inner(); // expected-note{{previous call is here}} inner(); // expected-warning{{'inner' parameter marked 'called_once' is called twice}} }); outer(); // expected-note{{previous call is here}} outer(); // expected-warning{{'outer' parameter marked 'called_once' is called twice}} } void never_called_one_exit(int cond, void (^callback)(void) CALLED_ONCE) { if (!cond) // expected-warning{{'callback' parameter marked 'called_once' is never called when taking true branch}} return; callback(); } void never_called_if_then_1(int cond, void (^callback)(void) CALLED_ONCE) { if (cond) { // expected-warning{{'callback' parameter marked 'called_once' is never called when taking true branch}} } else { callback(); } } void never_called_if_then_2(int cond, void (^callback)(void) CALLED_ONCE) { if (cond) { // expected-warning{{'callback' parameter marked 'called_once' is never called when taking true branch}} // This way the first statement in the basic block is different from // the first statement in the compound statement (void)cond; } else { callback(); } } void never_called_if_else_1(int cond, void (^callback)(void) CALLED_ONCE) { if (cond) { // expected-warning{{'callback' parameter marked 'called_once' is never called when taking false branch}} callback(); } else { } } void never_called_if_else_2(int cond, void (^callback)(void) CALLED_ONCE) { if (cond) { // expected-warning{{'callback' parameter marked 'called_once' is never called when taking false branch}} callback(); } } void never_called_two_ifs(int cond1, int cond2, void (^callback)(void) CALLED_ONCE) { if (cond1) { // expected-warning{{'callback' parameter marked 'called_once' is never called when taking false branch}} if (cond2) { // expected-warning{{'callback' parameter marked 'called_once' is never called when taking true branch}} return; } callback(); } } void never_called_ternary_then(int cond, void (^other)(void), void (^callback)(void) CALLED_ONCE) { return cond ? // expected-warning{{'callback' parameter marked 'called_once' is never called when taking true branch}} other() : callback(); } void never_called_for_false(int size, void (^callback)(void) CALLED_ONCE) { for (int i = 0; i < size; ++i) { // expected-warning@-1{{'callback' parameter marked 'called_once' is never called when skipping the loop}} callback(); break; } } void never_called_for_true(int size, void (^callback)(void) CALLED_ONCE) { for (int i = 0; i < size; ++i) { // expected-warning@-1{{'callback' parameter marked 'called_once' is never called when entering the loop}} return; } callback(); } void never_called_while_false(int cond, void (^callback)(void) CALLED_ONCE) { while (cond) { // expected-warning{{'callback' parameter marked 'called_once' is never called when skipping the loop}} callback(); break; } } void never_called_while_true(int cond, void (^callback)(void) CALLED_ONCE) { while (cond) { // expected-warning{{'callback' parameter marked 'called_once' is never called when entering the loop}} return; } callback(); } void never_called_switch_case(int cond, void (^callback)(void) CALLED_ONCE) { switch (cond) { case 1: callback(); break; case 2: callback(); break; case 3: // expected-warning{{'callback' parameter marked 'called_once' is never called when handling this case}} break; default: callback(); break; } } void never_called_switch_default(int cond, void (^callback)(void) CALLED_ONCE) { switch (cond) { case 1: callback(); break; case 2: callback(); break; default: // expected-warning{{'callback' parameter marked 'called_once' is never called when handling this case}} break; } } void never_called_switch_two_cases(int cond, void (^callback)(void) CALLED_ONCE) { switch (cond) { case 1: // expected-warning{{'callback' parameter marked 'called_once' is never called when handling this case}} break; case 2: // expected-warning{{'callback' parameter marked 'called_once' is never called when handling this case}} break; default: callback(); break; } } void never_called_switch_none(int cond, void (^callback)(void) CALLED_ONCE) { switch (cond) { // expected-warning{{'callback' parameter marked 'called_once' is never called when none of the cases applies}} case 1: callback(); break; case 2: callback(); break; } } enum YesNoOrMaybe { YES, NO, MAYBE }; void exhaustive_switch(enum YesNoOrMaybe cond, void (^callback)(void) CALLED_ONCE) { switch (cond) { case YES: callback(); break; case NO: callback(); break; case MAYBE: callback(); break; } // no-warning } void called_twice_exceptions(void (^callback)(void) CALLED_ONCE) { // TODO: Obj-C exceptions are not supported in CFG, // we should report warnings in these as well. @try { callback(); callback(); } @finally { callback(); } } void noreturn_1(int cond, void (^callback)(void) CALLED_ONCE) { if (cond) { exit(1); } else { callback(); } // no-warning } void noreturn_2(int cond, void (^callback)(void) CALLED_ONCE) { if (cond) { callback(); exit(1); } else { callback(); } // no-warning } void noreturn_3(int cond, void (^callback)(void) CALLED_ONCE) { if (cond) { exit(1); } callback(); // no-warning } void noreturn_4(void (^callback)(void) CALLED_ONCE) { exit(1); // no-warning } void noreturn_5(int cond, void (^callback)(void) CALLED_ONCE) { if (cond) { // NOTE: This is an ambiguous case caused by the fact that we do a backward // analysis. We can probably report it here, but for the sake of // the simplicity of our analysis, we don't. if (cond == 42) { callback(); } exit(1); } callback(); // no-warning } void never_called_noreturn_1(int cond, void (^callback)(void) CALLED_ONCE) { // expected-warning@-1{{'callback' parameter marked 'called_once' is never called}} if (cond) { exit(1); } } void double_call_noreturn(int cond, void (^callback)(void) CALLED_ONCE) { callback(); // expected-note{{previous call is here}} if (cond) { if (cond == 42) { callback(); // expected-warning{{'callback' parameter marked 'called_once' is called twice}} } exit(1); } } void call_with_check_1(void (^callback)(void) CALLED_ONCE) { if (callback) callback(); // no-warning } void call_with_check_2(void (^callback)(void) CALLED_ONCE) { if (!callback) { } else { callback(); } // no-warning } void call_with_check_3(void (^callback)(void) CALLED_ONCE) { if (callback != NULL) callback(); // no-warning } void call_with_check_4(void (^callback)(void) CALLED_ONCE) { if (NULL != callback) callback(); // no-warning } void call_with_check_5(void (^callback)(void) CALLED_ONCE) { if (callback == NULL) { } else { callback(); } // no-warning } void call_with_check_6(void (^callback)(void) CALLED_ONCE) { if (NULL == callback) { } else { callback(); } // no-warning } int call_with_check_7(int (^callback)(void) CALLED_ONCE) { return callback ? callback() : 0; // no-warning } void unreachable_true_branch(void (^callback)(void) CALLED_ONCE) { if (0) { } else { callback(); } // no-warning } void unreachable_false_branch(void (^callback)(void) CALLED_ONCE) { if (1) { callback(); } // no-warning } void never_called_conv_1(void (^completionHandler)(void)) { // expected-warning@-1{{completion handler is never called}} } void never_called_conv_2(void (^completion)(void)) { // expected-warning@-1{{completion handler is never called}} } void never_called_conv_WithCompletion(void (^callback)(void)) { // expected-warning@-1{{completion handler is never called}} } void indirectly_called_conv(void (^completionHandler)(void)) { indirect_conv(completionHandler); // no-warning } void escape_through_assignment_1(void (^callback)(void) CALLED_ONCE) { id escapee; escapee = callback; escape(escapee); // no-warning } void escape_through_assignment_2(void (^callback)(void) CALLED_ONCE) { id escapee = callback; escape(escapee); // no-warning } void escape_through_assignment_3(void (^callback1)(void) CALLED_ONCE, void (^callback2)(void) CALLED_ONCE) { id escapee1 = callback1, escapee2 = callback2; escape(escapee1); escape(escapee2); // no-warning } void not_called_in_throw_branch_1(id exception, void (^callback)(void) CALLED_ONCE) { if (exception) { @throw exception; } callback(); } void not_called_in_throw_branch_2(id exception, void (^callback)(void) CALLED_ONCE) { // expected-warning@-1{{'callback' parameter marked 'called_once' is never called}} if (exception) { @throw exception; } } void conventional_error_path_1(int error, void (^completionHandler)(void)) { if (error) { // expected-warning@-1{{completion handler is never called when taking true branch}} // This behavior might be tweaked in the future return; } completionHandler(); } void conventional_error_path_2(int error, void (^callback)(void) CALLED_ONCE) { // Conventions do not apply to explicitly marked parameters. if (error) { // expected-warning@-1{{'callback' parameter marked 'called_once' is never called when taking true branch}} return; } callback(); } void suppression_1(void (^callback)(void) CALLED_ONCE) { // This is a way to tell the analysis that we know about this path, // and we do not want to call the callback here. (void)callback; // no-warning } void suppression_2(int cond, void (^callback)(void) CALLED_ONCE) { if (cond) { (void)callback; // no-warning } else { callback(); } } void suppression_3(int cond, void (^callback)(void) CALLED_ONCE) { // Even if we do this on one of the paths, it doesn't mean we should // forget about other paths. if (cond) { // expected-warning@-1{{'callback' parameter marked 'called_once' is never used when taking false branch}} (void)callback; } } @interface TestBase : NSObject - (void)escape:(void (^)(void))callback; - (void)indirect_call:(void (^)(void))CALLED_ONCE callback; - (void)indirect_call_conv_1:(int)cond completionHandler:(void (^)(void))completionHandler; - (void)indirect_call_conv_2:(int)cond completionHandler:(void (^)(void))handler; - (void)indirect_call_conv_3WithCompletion:(void (^)(void))handler; - (void)indirect_call_conv_4:(void (^)(void))handler __attribute__((swift_async(swift_private, 1))); - (void)exit:(int)code NORETURN; - (int)condition; @end @interface TestClass : TestBase @property(strong) NSMutableArray *handlers; @property(strong) id storedHandler; @property int wasCanceled; @property(getter=hasErrors) int error; @end @implementation TestClass - (void)double_indirect_call_1:(void (^)(void))CALLED_ONCE callback { [self indirect_call:callback]; // expected-note{{previous call is here}} [self indirect_call:callback]; // expected-warning{{'callback' parameter marked 'called_once' is called twice}} } - (void)double_indirect_call_2:(void (^)(void))CALLED_ONCE callback { [self indirect_call_conv_1:0 // expected-note{{previous call is here}} completionHandler:callback]; [self indirect_call_conv_1:1 // expected-warning{{'callback' parameter marked 'called_once' is called twice}} completionHandler:callback]; } - (void)double_indirect_call_3:(void (^)(void))completionHandler { [self indirect_call_conv_2:0 // expected-note{{previous call is here}} completionHandler:completionHandler]; [self indirect_call_conv_2:1 // expected-warning{{completion handler is called twice}} completionHandler:completionHandler]; } - (void)double_indirect_call_4:(void (^)(void))completion { [self indirect_call_conv_2:0 // expected-note{{previous call is here}} completionHandler:completion]; [self indirect_call_conv_2:1 // expected-warning{{completion handler is called twice}} completionHandler:completion]; } - (void)double_indirect_call_5:(void (^)(void))withCompletionHandler { [self indirect_call_conv_2:0 // expected-note{{previous call is here}} completionHandler:withCompletionHandler]; [self indirect_call_conv_2:1 // expected-warning{{completion handler is called twice}} completionHandler:withCompletionHandler]; } - (void)double_indirect_call_6:(void (^)(void))completionHandler { [self indirect_call_conv_3WithCompletion: // expected-note{{previous call is here}} completionHandler]; [self indirect_call_conv_3WithCompletion: // expected-warning{{completion handler is called twice}} completionHandler]; } - (void)double_indirect_call_7:(void (^)(void))completionHandler { [self indirect_call_conv_4: // expected-note{{previous call is here}} completionHandler]; [self indirect_call_conv_4: // expected-warning{{completion handler is called twice}} completionHandler]; } - (void)never_called_trivial:(void (^)(void))CALLED_ONCE callback { // expected-warning@-1{{'callback' parameter marked 'called_once' is never called}} filler(); } - (void)noreturn:(int)cond callback:(void (^)(void))CALLED_ONCE callback { if (cond) { [self exit:1]; } callback(); // no-warning } - (void)escaped_one_path:(int)cond callback:(void (^)(void))CALLED_ONCE callback { if (cond) { [self escape:callback]; // no-warning } else { callback(); } } - (void)block_call_1:(void (^)(void))CALLED_ONCE callback { // We consider captures by blocks as escapes [self indirect_call:(^{ callback(); })]; callback(); // no-warning } - (void)block_call_2:(int)cond callback:(void (^)(void))CALLED_ONCE callback { [self indirect_call: ^{ if (cond) { // expected-warning@-1{{'callback' parameter marked 'called_once' is never used when taking false branch}} [self escape:callback]; } }]; } - (void)block_call_3:(int)cond completionHandler:(void (^)(void))callback { [self indirect_call: ^{ if (cond) { // expected-warning@-1{{completion handler is never used when taking false branch}} [self escape:callback]; } }]; } - (void)block_call_4WithCompletion:(void (^)(void))callback { [self indirect_call: ^{ if ([self condition]) { // expected-warning@-1{{completion handler is never used when taking false branch}} [self escape:callback]; } }]; } - (void)never_called_conv:(void (^)(void))completionHandler { // expected-warning@-1{{completion handler is never called}} filler(); } - (void)indirectly_called_conv:(void (^)(void))completionHandler { indirect_conv(completionHandler); // no-warning } - (void)never_called_one_exit_conv:(int)cond completionHandler:(void (^)(void))handler { if (!cond) // expected-warning{{completion handler is never called when taking true branch}} return; handler(); } - (void)escape_through_assignment:(void (^)(void))completionHandler { _storedHandler = completionHandler; // no-warning } - (void)escape_through_copy:(void (^)(void))completionHandler { _storedHandler = [completionHandler copy]; // no-warning } - (void)escape_through_copy_and_autorelease:(void (^)(void))completionHandler { _storedHandler = [[completionHandler copy] autorelease]; // no-warning } - (void)complex_escape:(void (^)(void))completionHandler { if (completionHandler) { [_handlers addObject:[[completionHandler copy] autorelease]]; } // no-warning } - (void)test_crash:(void (^)(void))completionHandler cond:(int)cond { if (cond) { // expected-warning@-1{{completion handler is never used when taking false branch}} for (id _ in _handlers) { } [_handlers addObject:completionHandler]; } } - (void)conventional_error_path_1:(void (^)(void))completionHandler { if (self.wasCanceled) // expected-warning@-1{{completion handler is never called when taking true branch}} // This behavior might be tweaked in the future return; completionHandler(); } - (void)conventional_error_path_2:(void (^)(void))completionHandler { if (self.wasCanceled) // expected-warning@-1{{completion handler is never used when taking true branch}} // This behavior might be tweaked in the future return; [_handlers addObject:completionHandler]; } - (void)conventional_error_path_3:(void (^)(void))completionHandler { if (self.hasErrors) // expected-warning@-1{{completion handler is never called when taking true branch}} // This behavior might be tweaked in the future return; completionHandler(); } - (void)conventional_error_path_3:(int)cond completionHandler:(void (^)(void))handler { if (self.wasCanceled) // expected-warning@-1{{completion handler is never called when taking true branch}} // TODO: When we have an error on some other path, in order not to prevent it from // being reported, we report this one as well. // Probably, we should address this at some point. return; if (cond) { // expected-warning@-1{{completion handler is never called when taking false branch}} handler(); } } #define NSAssert(condition, desc, ...) NSLog(desc, ##__VA_ARGS__); - (void)empty_base_1:(void (^)(void))completionHandler { NSAssert(0, @"Subclass must implement"); // no-warning } - (void)empty_base_2:(void (^)(void))completionHandler { // no-warning } - (int)empty_base_3:(void (^)(void))completionHandler { return 1; // no-warning } - (int)empty_base_4:(void (^)(void))completionHandler { NSAssert(0, @"Subclass must implement"); return 1; // no-warning } - (int)empty_base_5:(void (^)(void))completionHandler { NSAssert(0, @"%@ doesn't support", [self class]); return 1; // no-warning } #undef NSAssert #define NSAssert(condition, desc, ...) \ if (!(condition)) { \ NSLog(desc, ##__VA_ARGS__); \ } - (int)empty_base_6:(void (^)(void))completionHandler { NSAssert(0, @"%@ doesn't support", [self class]); return 1; // no-warning } #undef NSAssert #define NSAssert(condition, desc, ...) \ do { \ NSLog(desc, ##__VA_ARGS__); \ } while (0) - (int)empty_base_7:(void (^)(void))completionHandler { NSAssert(0, @"%@ doesn't support", [self class]); return 1; // no-warning } - (void)two_conditions_1:(int)first second:(int)second completionHandler:(void (^)(void))completionHandler { if (first && second) { // expected-warning@-1{{completion handler is never called when taking false branch}} completionHandler(); } } - (void)two_conditions_2:(int)first second:(int)second completionHandler:(void (^)(void))completionHandler { if (first || second) { // expected-warning@-1{{completion handler is never called when taking true branch}} return; } completionHandler(); } - (void)testWithCompletionHandler:(void (^)(void))callback { if ([self condition]) { // expected-warning@-1{{completion handler is never called when taking false branch}} callback(); } } - (void)testWithCompletion:(void (^)(void))callback { if ([self condition]) { // expected-warning@-1{{completion handler is never called when taking false branch}} callback(); } } - (void)completion_handler_wrong_type:(int (^)(void))completionHandler { // We don't want to consider completion handlers with non-void return types. if ([self condition]) { // no-warning completionHandler(); } } - (void)test_swift_async_none:(int)cond completionHandler:(void (^)(void))handler __attribute__((swift_async(none))) { if (cond) { // no-warning handler(); } } - (void)test_swift_async_param:(int)cond callback:(void (^)(void))callback __attribute__((swift_async(swift_private, 2))) { if (cond) { // expected-warning@-1{{completion handler is never called when taking false branch}} callback(); } } - (void)test_nil_suggestion:(int)cond1 second:(int)cond2 completion:(void (^)(void))handler { if (cond1) { handler(); // expected-note@-1{{previous call is here; set to nil to indicate it cannot be called afterwards}} } if (cond2) { handler(); // expected-warning{{completion handler is called twice}} } } - (void)test_nil_suppression_1:(int)cond1 second:(int)cond2 completion:(void (^)(void))handler { if (cond1) { handler(); handler = nil; // no-warning } if (cond2) { handler(); } } - (void)test_nil_suppression_2:(int)cond1 second:(int)cond2 completion:(void (^)(void))handler { if (cond1) { handler(); handler = NULL; // no-warning } if (cond2) { handler(); } } - (void)test_nil_suppression_3:(int)cond1 second:(int)cond2 completion:(void (^)(void))handler { if (cond1) { handler(); handler = 0; // no-warning } if (cond2) { handler(); } } @end