How can one avoid 'Assuming ‘variable’ is equal to nil' warning with custom assertion macros?

My custom assertion preprocessor macros trigger ‘Assuming ‘variable’ is equal to nil’ and ‘Null pointer passed as an argument to a 'nonnull' parameter’ compiler warnings during an Analyze Build with Xcode 6.4 if the value being checked is later in the test code passed to a function whose parameter cannot be NULL. These assertion preprocessor macros expand to an if statement which checks the condition and fail if the condition is not met.


e.g. - The macros are defined something along the lines of this.

#include <xpc/xpc.h>
#define ShouldBeTrue(condition_MacroParam, format_string_MacroParam, ...) \
do { \
if (!(condition_MacroParam)) { \
} \
} while(0)


An example call which triggers the warning:

const char *machServiceIdentifier = "com.test.XPCLA";
xpc_connection_t connection = xpc_connection_create_mach_service(machServiceIdentifier, NULL, 0);
ShouldBeTrue(connection != NULL, @"Connection to %s should not be NULL", machServiceIdentifier); // <-- 'Assuming 'connection' is equal to null'
xpc_connection_set_event_handler(connection, ^(xpc_object_t object) {});


If I instead use Cocoa's NSAssert() or XCTest’s XCTAssert() macros this compiler warning does not occur. How do Apple’s assertion macros manage to avoid this kind of warning?

Well, your assertion macro as shown doesn't do anything in the case that the assertion fails. However, you can teach the static analyzer about the function your assert macro uses to terminate the program or log the assertion. You need to mark such functions with a special attribute.

Ken Thomases wrote:

> Well, your assertion macro as shown doesn't do anything in the case that the assertion fails


Sorry, I should of been more clear that the compiler warnings are coming from the point of the if statement inside the macro. I removed the details of the if statement for clarity as they don't appear to be relevant to the warnings. The macro basically expands to code which calls a couple of functions, one of which sends a message to the current thread's Assertion Handler (a custom subclass of NSAssertionHandler that's set up at runtime) if the condition fails.


Thanks for the link. I look into that further.


However, neither Cocoa's NSAssert() nor XCTest’s XCTAssert() appear to be marked with the 'noreturn' attribute.


Since my initial post, I did some further experimentation and it seems that surrounding the conditional check with an try/catch block will silence the compiler warnings. This appears to be what might happening in the case of XCTAssert(), which expands to the _XCTPrimitiveAssertTrue() macro which has a try/catch block surrounding it's evaluation of the conditional expression.

The docs I linked say "The analyzer knows about several well-known assertion handlers", which is why (I assume) NSAssert() and XCTAssert() don't need to be marked with noreturn.


My point about your macro not doing anything in the "if" statement is that calling a noreturn function there is essential to getting the analyzer to understand that it's an assertion. Since, as far as it can tell, execution can continue past that point even if the condition isn't true, it has to analyze what happens in that case.


Also, a check for a pointer being nil or NULL actually causes the analyzer to consider that it may be nil/NULL when it might not have otherwise considered that. See, for example, this FAQ. Your assert macro which is not recognized as such actually makes the situation worse because it tells the analyzer that there is a chance that that pointer may be NULL. If there were no code which checked the pointer, the analyzer might assume that it's always non-NULL.

Ken Thomases wrote:

> My point about your macro not doing anything in the "if" statement is that calling a noreturn function there is essential to getting the analyzer to understand that it's an assertion.


Ok, I see. That helps clear things up as I misinterpreted what you were saying earlier.


Thanks for the other link.


This complicates matters with an assertion macro which expands the expression being checked inside the body of the macro. As that expanded code is beyond the control of the macro and could have additional issues beyond being NULL/not NULL. Thus, it would preferable to have Clang's static analyzer check inside the macro (i.e.- the expanded code of the macro) in case there's some issue with the expression from the caller.


I suppose I could have the macro set the result of the expanded expression into an variable and pass that variable to a custom should be true/false, should not be NULL, or other such assertion function that's marked noreturn.


Although, the try/catch clause workaround also appears to work.

Ken Thomaseswrote:

> The docs I linked say "The analyzer knows about several well-known assertion handlers", which is why (I assume) NSAssert() and XCTAssert() don't need to be marked with noreturn.


An additional question comes to mind.

NSAssert() and XCTAssert() are C macros, are they not? Thus, they'll be expanded at the Preprocessor stage and with XCTAssert() especially there doesn't seem to be anything left to say it's 'if statement' was inside of one of the "well-known assertion handlers" by the time the compilation stages roll around. Does the Clang static analyzer actually not only work around the point of the compilation stages but also before or during the Preprocessor stage?

I'm not quite sure I follow what you mean about how matters are complicated. The analyzer is analyzing the expanded code. When I wrote about the analyzer recognizing a macro as an assertion, I was being casual. It don't think it is analyzing in terms of what the macro is or is not, nor do I think it actually recognizes things as an assertion as such. What I mean is that the presence of a code path that leads to a noreturn call culls the possibility space that the analyzer is exploring. The analyzer then knows that the subsequent code is only ever reached when the condition that leads to noreturn does not hold. So, it has essentially internalized the implication of the assertion. That is, if you assert that "foo != NULL" and your assertion macro expands to code such that, if foo is NULL, it leads to a noreturn call, then the code after the assertion can only be reached if foo is actually not NULL.

I'm not sure what XCTAssert() expands to. But, as I mentioned in my other reply, the analyzer is working with the expanded code. So, it's recognizing certain well-known functions and methods as noreturn, not the macros as such. It's just that those macros use those well-known functions or methods or, put another way, the analyzer authors added the functions and methods used by those macros as well-known because they are used by those macros.

XCTAssert() expands to _XCTPrimitiveAssertTrue() which is defined in XCTestAssertionsImpl.h as such:

#define _XCTPrimitiveAssertTrue(test, expression, expressionStr, ...) \
({ \
    @try { \
        BOOL expressionValue = !!(expression); \
        if (!expressionValue) { \
            _XCTRegisterFailure(test, _XCTFailureDescription(_XCTAssertion_True, 0, expressionStr), __VA_ARGS__); \
        } \
    } \
    @catch (_XCTestCaseInterruptionException *interruption) { [interruption raise]; } \
    @catch (NSException *exception) { \
        _XCTRegisterFailure(test, _XCTFailureDescription(_XCTAssertion_True, 1, expressionStr, [exception reason]), __VA_ARGS__); \
    } \
    @catch (...) { \
        _XCTRegisterFailure(test, _XCTFailureDescription(_XCTAssertion_True, 2, expressionStr), __VA_ARGS__); \
    } \
})


If you make a copy of that macro and remove the calls to the XCTest API's there will not be a Clang static analyzer 'Null pointer passed as an argument to a 'nonnull' parameter' warning.

#define _ShouldAssertTrue(test, expression, expressionStr, ...) \
({ \
@try { \
BOOL expressionValue = !!(expression); \
if (!expressionValue) { \
} \
} \
@catch (NSException *exception) { \
} \
@catch (...) { \
} \
})


void function_which_cannot_be_passed_null(const char * __nonnull someStandInText);

__attribute__((__nonnull__(1)))
void function_which_cannot_be_passed_null(const char * __nonnull someStandInText) {
    return;
}

@interface someClass : NSObject
@end

@implementation someClass

- (void)someMethod {
    const char *someText = [@"some stuff" cStringUsingEncoding:[NSString defaultCStringEncoding]];
    _ShouldAssertTrue(self, NULL != someText, @"NULL != someText", @"%@", @"Should not be NULL.");

    function_which_cannot_be_passed_null(someText);
}

@end


However, if you remove the try/catch clause there will be an 'Null pointer passed as an argument to a 'nonnull' parameter' warning. e.g.-

#define _ShouldAssertTrue(test, expression, expressionStr, ...) \
({ \
BOOL expressionValue = !!(expression); \
if (!expressionValue) { \
} \
})


Thus, as far as XCTAssert() is concerned there may not be any special recognition of certain well-known functions and methods as noreturn going on.

Incidentally, there seems to be an inconsistency with the Clang static analyzer warnings. When a call to a macro (or if an statement) which checks for NULL that normally produces a 'Null pointer passed as an argument to a 'nonnull' parameter' warning is placed in the same method as a call to a macro which doesn't produce such a warning then no warning at all is produced with Xcode 6.4 (6E35b). Even if entirely different values are being checked for NULL. As illustrated in the example below:

#define _ShouldAssertTrue(test, expression, expressionStr, ...) \
({ \
BOOL expressionValue = !!(expression); \
if (!expressionValue) { \
} \
})


#define ShouldAssertTrueDifferently(expression) \
({ \
@try { \
BOOL expressionValue = !!(expression); \
if (!expressionValue) { \
} \
} \
@catch (NSException *exception) { \
} \
})


void function_which_cannot_be_passed_null(const char * __nonnull someStandInText);

__attribute__((__nonnull__(1)))
void function_which_cannot_be_passed_null(const char * __nonnull someStandInText) {
    return;
}

@interface someClass : NSObject
@end

@implementation someClass

- (void)someMethod {
    const char *someText = [@"some stuff" cStringUsingEncoding:[NSString defaultCStringEncoding]];
    _ShouldAssertTrue(self, NULL != someText, @"NULL != someText", @"%@", @"Should not be NULL.");
    function_which_cannot_be_passed_null(someText);
   
    // If you comment out the next few lines below there will be a 'Null pointer passed as an argument to a 'nonnull' parameter' Clang static analyzer warning in the above call to the _ShouldAssertTrue macro.
    const char *someDifferentText = [@"some Different text" cStringUsingEncoding:[NSString defaultCStringEncoding]];
    ShouldAssertTrueDifferently(NULL != someDifferentText);
    function_which_cannot_be_passed_null(someDifferentText);
}

@end

A simpler example, this time involving just a couple of if statements one of which is surrounded in an try/catch clause. Could this potentially be a bug in the Clang static analyzer?

void function_which_cannot_be_passed_null(const char * __nonnull someStandInText);

__attribute__((__nonnull__(1)))
void function_which_cannot_be_passed_null(const char * __nonnull someStandInText) {
    return;
}


@interface someClass : NSObject
@end

@implementation someClass

- (void)anotherMethod {
    const char *someText = [@"some stuff" cStringUsingEncoding:[NSString defaultCStringEncoding]];
    if (NULL == someText) {
        ;
    }
    function_which_cannot_be_passed_null(someText);
   
    // If you comment out the next several lines which follow there will be a 'Null pointer passed as an argument to a 'nonnull' parameter' Clang static analyzer warning at the previous if statement above.
    const char *someDifferentText = [@"Different text" cStringUsingEncoding:[NSString defaultCStringEncoding]];
    @try {
        if (NULL == someDifferentText) {
            ;
        }
    }
    @catch (NSException *exception) {
        [exception raise];
    }
    function_which_cannot_be_passed_null(someDifferentText);
}

@end

Yet another example, this time calling an entirely different function. As in the earlier example, each call and if statement involves a different variable and value. The Analyze Build was done with Xcode 6.4 (6E35b).

void function_which_cannot_be_passed_null(const char * __nonnull someStandInText);

__attribute__((__nonnull__(1)))
void function_which_cannot_be_passed_null(const char * __nonnull someStandInText) {
    return;
}

void another_function_which_cannot_be_passed_null(const char * __nonnull someStandInText);

__attribute__((__nonnull__(1)))
void another_function_which_cannot_be_passed_null(const char * __nonnull someStandInText) {
    return;
}


@interface someClass : NSObject
@end

@implementation someClass

- (void)anotherMethod {
    const char *someText = [@"some stuff" cStringUsingEncoding:[NSString defaultCStringEncoding]];
    if (NULL == someText) {
        ;
    }
    function_which_cannot_be_passed_null(someText);
   
    // If you comment out the next several lines which follow there will be a 'Null pointer passed as an argument to a 'nonnull' parameter' Clang static analyzer warning at the previous if statement above.
    const char *someDifferentText = [@"Different text" cStringUsingEncoding:[NSString defaultCStringEncoding]];
    @try {
        if (NULL == someDifferentText) {
            ;
        }
    }
    @catch (NSException *exception) {
        [exception raise];
    }
    another_function_which_cannot_be_passed_null(someDifferentText);
}

@end
How can one avoid 'Assuming ‘variable’ is equal to nil' warning with custom assertion macros?
 
 
Q