Is there a way I can get a warning when I'm assigning a variable instead of checking equality? There have been many times where I have accidentally assigned, rather than compared, and it would be great to have a warning!
Is there a way Xcode can warn me without having to change coding styles to:
if (YES == aVariable) {...}
Xcode already warns you if you use = instead of == in an if statement in most cases. If you're not getting the warning, tell us what version of Xcode you're using, how old your project is, and what build settings you have changed from their defaults.
My test: I created a brand new iOS app in Xcode 4.5.2 and didn't change any build settings. I just added a little code to application:didFinishLaunchingWithOptions: to trigger the warning. Here it is:
You can disable the warning by setting the compiler's -Wno-parentheses flag (but why would you want to?). You can suppress the warning in a particular case by adding an extra set of parentheses around the assignment:
if ((x = 7)) {
There are two cases where you don't get the warning by default. First, in an init method, you can assign to self, like this:
- (id)init {
if (self = [super init]) { // no warning by default
...
Second, in any context, you can assign the result of the nextObject selector, like this:
while (object = [enumerator nextObject]) { // no warning by default
You can enable warnings in these cases by setting the compiler's -Widiomatic-parentheses flag.
The compiler flag Wparentheses should do the trick. It will force you to place parentheses around assignments in a conditional in order to compile. Clang has this flag set by default.
Related
With code like this:
- (nonnull NSString *)testing {
return nil;
}
Shouldn't I get a compiler warning? I get no warning all, which seems to make the entire nullability stuff seem useless?
Well, in my opinion it should produce a warning, but I couldn't figure out to get one either.
What might be helpful though, is using Product > Analyze to run the CLANG Static Analyzer. This should give the following hint:
Null is returned from a method that is expected to return a non-null value
Another thing worth mentioning is the setting CLANG_WARN_NULLABLE_TO_NONNULL_CONVERSION which is named Incorrect Uses of Nullable values in the Apple LLVM 7.1 - Warnings - All languages section of the Build Settings.
This setting will not produce a warning for incorrect return values, but it shows a warning when using the method with incorrect parameters (e.g. nil for nonnull parameters).
This answer refers to Xcode Version 7.3.1 (7D1014)
"nonnull" is mostly there to combine Objective-C and Swift. Swift will translate the type of the method to "NSString" and not "NSString?".
NSArray rather dislikes being passed a nil object as part of its constructor:
UIView *aView;
UIView *aSecondView = [[UIView alloc] init];
NSArray *array = #[aView, aSecondView];
will throw an exception at runtime when array is created.
Does clang have any facilities to try and detect this kind of error? For some trivial cases (like the one above: a stack-local variable that's never assigned to), it seems like the sort of problem the static analyzer would hit out of the park.
TL;DR: -Wuninitialized catches that specific example, __attribute__((nonnull)) for parameters to functions/methods would help catch nil as argument in general.
For this specific example (variable not specifically initialized), you can use -Wuninitialized to catch those uses:
○ xcrun clang a.m -F/System/Library/Frameworks -c -o a.o -Wuninitialized
a.m:6:22: warning: variable 'aView' is uninitialized when used here [-Wuninitialized]
NSArray *array = #[aView, aSecondView];
^~~~~
a.m:4:16: note: initialize the variable 'aView' to silence this warning
NSView *aView;// = nil;
^
= nil
1 warning generated.
For passing nullptr/NULL/nil to functions/methods, __attribute__((nonnull)) should work for most cases. Since this is an API provided by Apple, you would need to file a radar and hope they add it.
P.S: Actually, __attribute__((nonnull)) wouldn't “simply work”, in this case (if you initialized aView to nil, for example. The #[...] sequence seems to be creating an array, and calling +[NSArray arrayWithObjects: count:], where you would be able to mark the first argument as nonnull, but not the things pointed to by it. In any case, it should be relatively simple to write a clang pass for the analysis you mention. You should file a radar, since such a pass could avoid lots of time losses.
P.P.S: It seems __attribute__((nonnull)) doesn't propagate much information, which is sad. :-( If you have a function f(type arg __attribute__((nonnull))) and you pass it a variable which you initialized to nil and never touched again, it doesn't warn.
I believe you are right, Clang static analyzer hit this error out of the park. It is built into Xcode (6 Beta as I tested on). Go Building Settings > Static Analyzer - Analysis Policy > Set Analyze During 'Build' to Yes. To manually perform, go Product > Analyze.
Static analyzer can also be used as stand alone tool in command line. Other option to analyze at compile time is to add Run Script in Build Phases before Compile Sources.
In my application I have the following Objective-C code:
-(void)layoutPages
{
NSMutableArray* sections = [NSMutableArray array];
[sections addObject:[[NSAttributedString alloc] initWithString:#"Hello world"]];
for (NSAttributedString* contentSection in sections) {
NSLog(#"%#",contentSection);
}
}
Console output: 2014-04-22 14:11:01.505 MyApp[24784:830b] Hello world{}
If I compile for x86_64 architecture using -Os optimization, LLVM then silently optimizes out the loop variable 'contentSection'. When I use -O0, the bug disappears.
This is the output when I try to print the description of the contentSection variable:
(lldb) po contentSection
error: Couldn't materialize struct: the variable 'contentSection' has no location, it may have been optimized out
Errored out in Execute, couldn't PrepareToExecuteJITExpression
How is that possible? From my point of view a loop variable should never be optimized out when used inside the loop. I have seen that other people have a similar issue with LLVM but not with a loop variable. Could this be a compiler bug?
This is probably a compiler settings issue. First you'll want to check that your run scheme is not in release mode. Go to "Edit scheme..." -> "Run" -> "Info" -> "Build Configuration". Make sure the value is set to "Debug".
If that is not the problem, then make sure your debug build settings don't have compiler optimization turned on. Make sure "Optimization Level" is set to "none" for debug. Also make sure there is no other place where the compiler optimization levels might be set, such as in the "Other C Flags" setting.
I've opened up some old iOS code and when I try to build it I get an "unused parameter" error for code like this:
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
NSLog(#"Search Bar isn't used in this function");
}
This is the first time I've ever seen an Objective-C compiler spit out errors (not warnings) for this. Since a lot of iOS calls don't necessarily use the passing arguments (examples being a lot of callbacks), I need help in getting rid of this.
Solution # 1)
In your Xcode project's "Build Settings", there's a parameter for "Unused Parameters".
Reset that from YES to NO.
Solution # 2 (available with Xcode 4):
In Xcode 4.3.2 or higher use __unused.
(THANKS to Tim Bodeit's comment below)
Solution # 3)
Put #pragma unused (searchBar) in your code, preferably right underneath the line in your implementation where the function is declared.
I.E.
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
#pragma unused (searchBar)
NSLog(#"Search Bar isn't used in this function");
}
I understand exactly why unused variable warnings occur. I don't want to suppress them in general, because they are incredibly useful in most cases. However, consider the following (contrived) code.
NSError *error = nil;
BOOL saved = [moc save:&error];
NSAssert1(saved, #"Dude!!1! %#!!!", error);
Xcode reports that saved is an unused variable, when of course it isn't. I suspect this is because NSAssert1 is a macro. The NS_BLOCK_ASSERTIONS macro is not defined, so Objective C assertions are definitely enabled.
While it doesn't hurt anything, I find it untidy and annoying, and I want to suppress it, but I'm not sure how to do so. Assigning the variable to itself gets rid of the compiler warning, but I'd rather do it the "right" way if such a thing exists.
I'm unsure if it's still supported in the new LLVM compiler, but GCC has an "unused" attribute you can use to suppress that warning:
BOOL saved __attribute__((unused)) = [moc save:&error];
Alternatively (in case LLVM doesn't support the above), you could split the variable declaration into a separate line, guaranteeing that the variable would be "used" whether the macro expands or not:
BOOL saved = NO;
saved = [moc save:&error];
Using Xcode 4.3.2 and found out that this seems to work (less writing)
BOOL saved __unused;
In Xcode you can set the warnings for "Unused Variables." Go to "Build Settings" for the target and filter with the word "unused"
Here is a screenshot:
I suggest you only change it for Debug. That way you don't miss anything in your release version.
NSError *error = nil;
BOOL saved = [moc save:&error];
NSAssert1(saved, #"Dude!!1! %#!!!", error);
#pragma unused(saved)
Try like this.
It is working for me. It will work for you, too.
The only simple and portable way to mark variable as used is… to use it.
BOOL saved = ...;
(void)saved; // now used
You may be happy with already described compiler-specific extensions, though.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-variable"
NSUInteger abc; /// Your unused variable
#pragma clang diagnostic pop
SOURCE
try with: __unused attribute. Works in Xcode 5
This is the way you do it in C and therefore also Objective-C.
Even though you do not have warnings enabled, it's always a good idea to mark the return value as explicitly ignored. It also goes to show other developers, that you have not just forgotten about the return value – you have indeed explicitly chosen to ignore it.
(void)[moc save:&error];
EDIT: Compilers ignore casts to void, so it should not affect performance – it's just a nice clean human annotation.
You can set "No" LLVM compliler 2.0 warning on "Release"
Make it take up two lines. Separate the declaration and default value
BOOL enabled = NO;
// ...
BOOL enabled;
enabled = NO;