Clang nullability warnings and how to approach them - ios

Recently I turned on CLANG_WARN_NULLABLE_TO_NONNULL_CONVERSION in Xcode and I am overwhelmed with nullability related warnings in my Objective-C code. The one warning type that is most prevalent is Implicit conversion from nullable pointer 'TypeA * _Nullable' to non-nullable pointer type 'TypeA * _Nonnull'.
I started my attempt to remove with these warnings by creating a local of the same type in a method as described here.
https://www.mail-archive.com/xcode-users%40lists.apple.com/msg02260.html
This article says by first using a local, that object is of attribute unspecified nullable, so it can be used as legit parameter to the methods expecting nonnull.
But I feel this is a cop out move and really not solving the issue in any beneficial way.
Has anyone gone through this exercise already? I would be grateful if you can share a strategy that you took.

Actually, I have messed around with that topic for a little bit. I wanted to improve nullability situation in a somewhat big project (make it more 'swiftier'). Here is what I found.
Firstly, you should turn on CLANG_WARN_NULLABLE_TO_NONNULL_CONVERSION (-Wnullable-to-nonnull-conversion)
Secondly, about
first using a local, that object is of attribute unspecified nullable,
so it can be used as legit parameter to the methods expecting nonnull.
This smells bad, and I created a macro called NONNUL_CAST(). Here is example how to implement it:
#define NONNUL_CAST(__var) ({ NSCAssert(__var, #"Variable is nil");\
(__typeof(*(__var))* _Nonnull)__var; })
Here you can see hacky __typeof(*(__var))* _Nonnull)__var, but it is not so bad. If __var is of type A* _Nullable we dereference __var, so it's type now just A, after we make reference again, but _Nonnull, and get nonnull __var as answer. Of course we assert to, in case something goes wrong.
Thirdly, you must specify nullabilty on every local variable, and you should put all your code in NS_ASSUME_NONNULL_BEGIN/END, like this:
NS_ASSUME_NONNULL_BEGIN
<your_code_goes_here>
NS_ASSUME_NONNULL_END`
You put there EVERY LINE (except imports) of your code, in .h and .m files. That will assume that all your arguments of methods, return types and properties are nonnull. If you want to make it nullable, put nullable there.
So, all done, now what?
Here is example of typical usage:
- (Atype*)makeAtypeWithBtype:(nullable BType*)btype {
Atype* _Nullable a = [btype makeAtype];
if (a) {
// All good here.
return NONNUL_CAST(a);
} else {
// a appeared as nil. Some fallback is needed.
[self reportError];
return [AtypeFactory makeDeafult];
}
}
Now you have more robust nullability situation. May be it is not looking nicely, but it is objective-c, so nothing to complain about.

Not every warning makes sense. Sometimes it's a shortcoming in the compiler. For instance, this code doesn't need a warning.
- (nullable id)transformedValue:(nullable id)value {
id result = value != nil ? UIImageJPEGRepresentation(value, 1.0) : nil;
return result;
}
We are checking to see if it's null! What more can we do? Why create an extra pointer?
So, we do this:
- (nullable id)transformedValue:(nullable id)value {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnullable-to-nonnull-conversion"
id result = value != nil ? UIImageJPEGRepresentation(value, 1.0) : nil;
#pragma clang diagnostic pop
return result;
}
Why is this the proper answer?
First, it's OK to be smarter than the compiler. You don't want to start trashing your code, just because of a bogus warning.
This solution specifies the exact warning message to suppress, and it suppresses it for just one line.

Related

Objective-C pointer and swift

I'm following an apple document, but unfortunately the examples are written on objective-c, but I have confidence with Swift language and can not understand the meaning of some things, in particular, in this example:
void RunLoopSourcesPerformRoutine (void *info){
RunLoopSource* obj = (RunLoopSource*)info;
[obj sourceFired];
}
this line: RunLoopSource* obj = (RunLoopSource*)info;
the parameter: void *info indicates that info is a pointer to void, then I can put the address of any type of data structure, following various apple documents I saw that the translation of this : void *info into swift language is :
info: UnsafeMutableRawPointer?
Now, the RunLoopSource* obj = (RunLoopSource*)info; line indicates that obj is a variable of type: RunLoopSource, and to this is assigned the value of (RunLoopSource *) info, but precisely What does it mean this statement? : (RunLoopSource *) info, and how it translates in swift language ?
Swift really hates pointer. These 2 lines of code can be converted to Swift as
func RunLoopSourcesPerformRoutine(info: UnsafeMutableRawPointer) {
let obj = info.assumingMemoryBound(to: RunLoopSource.self)
obj.pointee.sourceFired()
}
This specific expression is a "typecast": it's saying that info, which is declared to be a pointer-to-unknown-anything (void *) is actually known by the programmer to be a pointer to a RunLoopSource. This forcibly changes the type of the expression to make the compiler happy as it is assigned to obj.
It is equivalent to using as! in Swift and is idiomatic when you know the semantics of a void * but the syntax doesn't capture it.
(This attempts to answer your question as stated but I'm not sure if you are looking for more information. If so, please clarify and me or someone more expert in unsafe pointers in Swift can help out.)
What you are dealing with (void *info) is a C pointer-to-void, which arrives into Swift as a form of UnsafeRawPointer. This means that type info has been cast away and that memory is being managed elsewhere.
In order to work with this thing as what you believe it to be, i.e. a RunLoopSource, you need to characterize it explicitly as a RunLoopSource. In C, you would cast, as in the example code you posted: (RunLoopSource*)info. In Swift, you rebind.
Observe that in your case this whole thing has been made just a little more complicated by the fact that this UnsafeMutableRawPointer has been wrapped in an Optional, and will have to be unwrapped before you can do anything at all.
Assuming, then, in your case, that info is really an UnsafeMutableRawPointer? bound to a RunLoopSource, you can say:
let rlsptr = info!.assumingMemoryBound(to: RunLoopSource.self)
let rls = rlsptr.pointee
Now rls is a RunLoopSource and you can work with it however you like. Keep in mind, however, that the memory is unmanaged, so you should work with it only here and now.
EDIT By the way, Apple has a really nice document on this entire matter: https://swift.org/migration-guide/se-0107-migrate.html

Is it possible to silence the warning on "performSelector" and receive the return value or object?

I am familiar with the solutions to this ARC warning (performSelector may cause a leak because its selector is unknown) and have implemented them in most cases, but I can't seem to find a way to properly get the return value for a selector without just suppressing a warning.
It seems that maybe it can't or shouldn't be done, but a rewrite of code logic (developed by others) is too time consuming.
Code example:
NSString *message = [callback performSelector:validatorSel withObject:textCell.textField.text];
If validatorSel is known to not to begin with allocor new, or to have copy (or Copy) in its name, and you know there are no memory-management overrides involved (which are rare), then the default memory management will be correct here, and you can suppress the warning with an appropriate #pragma. If you cannot prove those things, then this may crash, which is why there's a warning.
If you cannot prove the above requirements, then there is no way to make this safe under ARC. You will either have to build it without ARC or rewrite it.
From your code sample it looks like you are expecting a selector for a method which takes an NSText * and return an NSString *. So from your linked answer you can determine that the implementation of this method has the function type:
NSString *(*)(ID, SEL, NSText *)
Here ID may be replaced by the type of callback, and NSText * can be replaced by the actual type of textCell.textField.text if our guess is wring.
Again from your linked answer, you can obtain the implementation and call it using:
NSString *(*implementation)(ID, SEL, NSText *)
= (void *)[callback methodForSelector: performSelector:validatorSel];
NSString *message = implementation(callback, validatorSel, textCell.textField.text);
As #RobNapier correctly points out this is only safe under ARC if the selector does not return a retained value, i.e. for normal[*] selectors if it is a member of the init, copy, or new method families. Now you are very unlikely to be passed an init family method for validatorSel as that would require callback to be a reference to an alloc'ed but not init'ed object, so we can ignore that one for now[#]. To test for the other two families you can use code along the lines of:
NSString *message; // for the return value of the selector
NSString *selName = NSStringFromSelector(validatorSel); // get string name of selector
if ([selName hasPrefix:#"new"] // starts with new,
|| [selName hasPrefix:#"copy"] // or copy,
|| [selName rangeOfString:#"Copy"].location != NSNotFound) // or contains "Copy"
{
// need to handle returning a retained object
...
}
else
{
// normal case
NSString *(*implementation)(ID, SEL, NSText *)
= (void *)[callback methodForSelector: performSelector:validatorSel];
message = implementation(callback, validatorSel, textCell.textField.text);
}
Which just leaves how to handle the return value correctly under ARC for copy and new family methods...
Handling copy and new family methods
ARC knows that a method, or function, returns a retained object by an attribute being placed on the method/function type. The naming convention is just the language's way of inferring the attribute if it is not present, it can be manually specified using the NS_RETURNS_RETAINED macro on a method/function declaration. So the missing code above is just:
{
// need to handle returning a retained object
NSString *(*implementation)(ID, SEL, NSText *) NS_RETURNS_RETAINED
= (void *)[callback methodForSelector: performSelector:validatorSel];
message = implementation(callback, validatorSel, textCell.textField.text);
}
The modified type for implementation tells ARC that it will return a retained object and ARC will handle the call the same way it does for known copy or new family methods.
HTH
Note: Handling init family methods
We skipped the init family not just because it is highly unlikely but also because it behaves differently - init family methods consume the object reference they are called on, that is they expect to be passed an owned object which they take ownership of, and will release it if needed. Unsurprisingly consuming an argument is also indicated by an attribute, just as for returning a retained object. The curious reader might wish to determine the code required, even though needing it is highly unlikely.
[*] A "normal" selector is one for a method which follows the standard naming conventions of Objective-C and does not use attributes to alter the memory ownership behaviour in ways contrary to the standard conventions. Only supporting standard conventions is not a big restriction, the whole point of the conventions is that code relies on them!
[#] You are of course very unlikely to be passed a new family selector as well, callback would usually have to be a reference to a class object, but handling it is the same as for the copy family so we've included it.

In Objective-C, does the binding of method really happen at "run-time"?

I heard that Objective-C is influenced by the "message passing mechanism" of SmallTalk.
Objective-C, like Smalltalk, can use dynamic typing: an object can be
sent a message that is not specified in its interface. This can allow
for increased flexibility, as it allows an object to "capture" a
message and send the message to a different object that can respond to
the message appropriately, or likewise send the message on to another
object.
And I felt for codes like [anObject someMethod], the binding of someMethod to the machine code may happen at run-time..
Therefore, I write a demo like this:
#import <Foundation/Foundation.h>
#interface Person : NSObject {
#private char *name;
}
#property (readwrite, assign) char *name;
- (void)sayHello;
#end
#implementation Person
#synthesize name;
- (void)sayHello {
printf("Hello, my name is %s!\n", [self name]);
}
#end
int main() {
Person *brad = [Person new];
brad.name = "Brad Cox";
[brad sayHello];
[brad sayHelloTest];
}
I tried [brad sayHelloTest] to send brad a message sayHelloTest which brad doesn't know how to handle with.. I expect the error will NOT happen at compile-time..
However, the compiler still throws an error:
main.m:24:11: error: instance method '-sayHelloTest' not found (return type defaults to 'id') [-Werror,-Wobjc-method-access]
[brad sayHelloTest];
^~~~~~~~~~~~
main.m:3:12: note: receiver is instance of class declared here
#interface Person : NSObject {
^
Change [(id)brad sayHelloTest] to [(id)brad sayHelloTest]; doesn't work either.. (The compiling command is clang -Wall -Werror -g -v main.m -lobjc -framework Foundation -o main)
In Objective-C, does the binding of method really happen at "run-time"? If so, why will there be a compiler error like this?
If the binding doesn't happen at "run-time", why was "Objective-C" called "dynamic typing language"?
Does anyone have any ideas about this?
One job of a compiler is to catch as many errors at compile time as possible. If it can tell that the call will fail at runtime, you generally want it to complain.
You can suppress this via casting to show that runtime resolution is happening:
[(id)brad sayHelloTest];
Because the IDE can infer the obvious error from the context.
When you write if (a = 1),you will get a warning. A good IDE should help you find mistakes as early as possible.
I figured out the reason finally..
It throw errors during compiling because -Werror flag is included, which will turn warning into error..
http://clang.llvm.org/docs/UsersManual.html#cmdoption-Werror
After I delete -Werror flag, everything works as expected and the error only happens at run-time.
It has become a compiler error only within the last five years for there to be no known declaration of a method. It has to do with Automatic Reference Counting. Under ARC, the compiler is now responsible for the reference-counting-based memory management that Cocoa uses.
Given that responsibilty, it must be able to see the declarations of methods for any messages before they are sent, so that it knows what retains and releases are appropriate.
The method resolution (the lookup of the method on the class) does still happen at runtime, and -- particularly if you disable ARC -- you can still take advantage of message forwarding.
One way around ARC's requirement was given by Marcelo Cantos -- cast the receiver to id. Another is to use performSelector:. A third -- though I can't recommend it -- is to use objc_msgSend() directly.
Note that the "binding" of the method does, and always did, happen at compile time. Methods are associated with classes, when the classes are defined. Messages are distinct from methods, and it is they that resolve at runtime to a method.

Can I enable warning for comparing a enum and a non enum values?

I recently debugged an issue which was caused because an enum was being compared with a non-enum value. Here is a simplified example:
typedef NS_ENUM(NSInteger, MyType) {
TypeVal1,
};
...
MyType type = TypeVal1;
int randomValue = 0;
BOOL compareTypeAndPrimiative = (randomValue == typeA); // No warning
Is it possible to turn on a warning for this?
I could suppress if if needed by explicitly casting:
BOOL iKnowWhatImDoing = (randomValue == (int) typeA);
There is no support for this kind of warning because C enums are not strongly typed, and I believe the standards require them to be treated as ints (or unsigned ints). Comparing them with regular integers has always been allowed as part of the C standard, and a warning of this type would end up flagging a lot of correct code. I'm sure somebody can link to the appropriate section of the C standards.
Particularly with iOS and Apple APIs, you will find that enum values are often used as bitmasks. In these situations it is common to write code like this:
if ((value & flag) == kFlag) { ... }
You could argue that using enums for this purpose is a bad idea, but you would probably end up having to disable this warning for all sorts of code.
I just ran through LLVM manual and it seems there is no option for this. Then I tried to turn on all the compiler warnings (-Weverything), however there was no warning your enum case.
So the answer is no.
The best solution to avoid such bugs is to name the variables/constants appropiately. Make obvious what the variable/constant represent. Then the error in comparison should be obvious, too.

Branch condition evaluates to a garbage value when dereferencing a pointer to a pointer

I'm trying to clean up some issues from xcode's analyzer. One I haven't found a solution to is the "Branch condition evaluates to a garbage value". It's occurring in the following way:
int methodToCloseMyDatabase(sqlite3 **myDatabase, const char *callingFunctionName)
{
if (myDatabase)
{
if (*myDatabase) // The warning is thrown here
{
// Do something
}
}
}
This error sounds to me as if CLang has analyzed your code and found that *myDatabase is not set to anything.
It could even be that the analyzer has found a possible code branch that does not set the value.
How did you set myDatabase? You probably forgot to initialize it correctly, so it points to arbitrary place in memory.

Resources