clang in XCode 14.3 'carrying over' nullability of objects (objc/c++) with _auto_type/auto [despite assert or inline if]

Hi, i noticed a bigger change in the latest clang and wanted to check if this is intentional or a beta bug. clang in XCode 14.3 is 'carrying over' nullability of objects (objc/c++) with _auto_type/auto [despite assert or inline if]

In 14.2 autotype/auto made an null_unspecified ptr allowing us to downcast / unwrap an object. In 14.3 this isnt possible anymore. => Is there still a way to do 'nullability unwrapping' of any reference in C(objC/C++)

tried but failed workarounds:

  • There is an easy workaround by looking at the dereferenced type but that doesnt work with dispatch bloks as they arent dereferencable
  • There is also a workaround with objc generics but I cant use that as there might be objects not usable as generics

example

The following is a sample that worked with 14.2 but throws warnings in 14.3 clang: xcrun clang -fobjc-arc -Wnullable-to-nonnull-conversion -Werror test.m

#import <Foundation/Foundation.h>

#include <stdio.h>
#include <stddef.h>
#include <stdint.h>

#define typename(x) _Generic((x),        /* Get the name of a type */             \
	id: "id", \
	default: "other")

#define fmt "%20s is '%s'\n"

_Pragma("clang diagnostic ignored \"-Wauto-var-id\"")

int main(int argc, char *argv[]) {
	@autoreleasepool {
		int temp = 666;

		NSString *_Nullable s = @"";
		id _Nullable s2 = @"";
		dispatch_block_t _Nullable s3 = dispatch_block_create(0, ^{});
		CFStringRef _Nullable s4 = CFSTR("");	
		int *_Nullable s5 = &temp;
		printf( fmt, "NSString (_Nullable", typename(s));
		printf( fmt, "id _Nullable", typename(s2));
		printf( fmt, "block _Nullable", typename(s3));
		printf( fmt, "CFStringRef _Nullable", typename(s4));
		printf( fmt, "int _Nullable", typename(s5));

		assert(s != nil);	
		assert(s2 != nil);
		assert(s3 != nil);
		assert(s4 != nil);
		assert(s5 != nil);

		//no modifier works if type is given
//		NSString *u = s;
//		id u2 = s2;
//		dispatch_block_t u3 = s3;
//		CFStringRef u4 = s4;
//		int *u5 = s5;
//		__auto_typeauto carries _nullable in 14.3 but not 14.2
		__auto_type u = s;
		__auto_type u_with_default = s != nil ? s : @"DEFAULT";
		__auto_type u2 = s2; 
		__auto_type u3 = s3;
		__auto_type u4 = s4;
		__auto_type u5 = s5;
		//auto and decltype have the same behaviour for C++ and even if we use an inline if
		//Q: how can I unwrap an optional [not just objc!]

		NSString *_Nonnull d = u;
		NSString *_Nonnull d_with_default = u_with_default;
		id _Nonnull d2 = u2;
		dispatch_block_t _Nonnull d3 = u3;
		CFStringRef _Nonnull d4 = u4;
		int *_Nonnull d5 = u5;
		printf( "-------\n");
		printf( fmt, "NSString", typename(d));
		printf( fmt, "NSString", typename(d_with_default));
		printf( fmt, "id", typename(d2));
		printf( fmt, "block", typename(d3));
		printf( fmt, "CFStringRef", typename(d4));
		printf( fmt, "int", typename(d5));
	}
}
Post not yet marked as solved Up vote post of Dominik.Pich Down vote post of Dominik.Pich
828 views

Replies

I recently noticed the same problem. Pretty annoying.

I create a issue report for that directly with the LLVM devs because I think this is caused by clang/LLVM and not by Apple, since Xcode 14.2 still used LLVM 14 whereas Xcode 14.3 uses LLVM 15. See also: https://github.com/llvm/llvm-project/issues/63018

Also ignoring if-statements violates the spec for that feature that clearly says:

On a branch, where a nullable pointer is known to be non null, the checker treat it as a same way as a pointer annotated as nonnull.

Source: https://clang.llvm.org/docs/analyzer/developer-docs/nullability.html

So basically:

__auto_type mightBeNull = ...;
if (mightBeNull) {
    &#x2F;&#x2F; clang knows for sure that `mightBeNull` is not Null here
    takesNonNullArg(mightBeNull); &#x2F;&#x2F; This is an error but according to spec it must not be one!
}

I used the following macro to fix nullability errors (they are warnings but I told clang to treat them as error):

#define assumeNotNull(_value)         \
({                                    \
    if (!_value) abort();             \
    __auto_type const _temp = _value; \
    _temp;                            \
})

but that stopped working. What still works is the following:

#define assumeNotNull(_value)         \
({                                    \
    if (!_value) abort();             \
    __auto_type const _temp = _value; \
    (typeof(*_temp) *_Nonnull)_temp;  \
})

but as you've already pointed out, this won't work for blocks!

Everyone who wants some progress on this is invited to upvote and comment on the LLVM issue linked above, as I think going that route will get your further than posting on this forum or sending a feedback to Apple, especially if it really is because some LLVM change because Apple is rather not interested in patching their LLVM build shipping with Xcode, especially not if that changes core compiler functionality and thus basically makes Apple's clang incompatible to all other clang distributions

The reason for that change might be this one: http://www.openradar.me/36877120