Xcode. Question about syntax error checking - objective-c

Xcode looked at this line and did not complain. Project built, code crashed at runtime.
NSString *randomName = [NSString stringWithFormat:#"%#, %#, %#",
[randomAjectiveList objectAtIndex:ajectiveIndex],
[randomNounList objectAtIndex:nounIndex]];
Naturally, come to think about it, i have one too many "%#" in place, one more then actual arguments. Correct code should look as follows
NSString *randomName = [NSString stringWithFormat:#"%#, %#",
[randomAjectiveList objectAtIndex:ajectiveIndex],
[randomNounList objectAtIndex:nounIndex]];
I ask you though ... why didn't Xcode complain? Seems like such an obvious thing to do with param counters. Shouldn't this be checked at compile time? Is it specific to "%#", perhaps?
Please advise.

Based on a quick check, you're 100% right that this isn't checked at compile time, seemingly even by the static analyser. Conversely, NSLog is checked. So on my machine, with XCode 4.0.2, the following:
[NSString stringWithFormat:#"%d %# %#"];
NSLog(#"%d %# %#");
Produces a warning on the NSLog of "More '%' conversions than data arguments" but fails to comment on the NSString.
So, the difference could be fixed function calls versus dynamic calls. The compiler can't actually be completely certain where the NSString call will go because it's possible you'll have declared a category or used the low-level runtime to adjust the NSString selector table at runtime.
However, especially given the problems you'll almost immediately encounter if you start modifying the behaviour of the Foundation classes, like you I'd have expected at least a warning.

Related

Can't find the source of this "Expected Identifier" error, any thoughts?

I'm very new to Objective-C and programming in general and I'm having some difficulty solving a strange error I'm getting in my code. I've rechecked my code line-by-line but no matter what I do I get an "Expected Identifier" error on this one line.
NSString *text = [NSString stringWithFormat:#"Your total is $%.2f", [self.cartTotal]];
I've retyped the line several times to make sure that I wasn't missing any typos, but a little red arrow is pointing to the first closing bracket.
Take [self.cartTotal] out of the brackets. They're unnecessary here.
Should be:
NSString *text = [NSString stringWithFormat:#"Your total is $%.2f", self.cartTotal];
To help understand the error (assuming you're coming from a language like Java or C++), what you've written is the equivalent to writing this in say Java:
this.cartTotal.
Notice the hanging dot at the end? That's what you've done here basically. In Java, that dot suggests you're calling a method on whatever object cartTotal is, or access a public variable on that object. I'm not sure what a Java or C++ error message would say here, but this is the equivalent scenario.
Given #GregParker's excellent comment...
You created cartTotal by way of a #property.
#property double cartTotal; // or something like this
This creates three things:
A setter
A getter
An instance variable
The setter is accessed in two ways:
[self setCartTotal:value];
Or:
self.cartTotal = value;
These both do the same thing.
The getter is likewise accessed in two ways:
[self cartTotal];
Or:
self.cartTotal;

Casting Objective-C Objects

I've been wondering this for a while, and Google hasn't provided me with the information I want. How fast does the casting process take? Does it depend on the number of fields an object has? Is it something to be avoided at all costs? Does it differ on x32, x64 and ARM machines?
Casting is only for the compiler to issue you better warnings. At runtime casting has no performance hit. All objects are just objects. You send messages to those objects.
The runtime doesn't care what type you gave when you had a pointer to that object in your code. It will send the message no matter what.
For example:
NSArray *myString = [NSString stringWithFormat:#"Hello"];
NSNumber *longerString = [(NSString *)myString stringByAppendingString:#" World"];
NSLog(#"%#", longerString);
Will log Hello World. You really give types to things so the compiler can check, but the runtime only knows that you're passing a message to an object. It will use the class of the object to look up the method to call from the message name, but it doesn't care what you typed at compile time.
You could have also done:
id myString = [NSString stringWithFormat:#"Hello"];
id longerString = [myString stringByAppendingString:#" World"];
NSLog(#"%#", longerString);
And the runtime will do the exact same thing, but the compiler will match up your types differently and generate warnings/errors based on different semantics (basically, does any object say it responds to this message).

Strange BAD_ACCESS when printing NSInteger

this is my first time asking something here, so don't be too harsh on me please :-). I have a strange "bad access" issue. In a class of mine I have a NSInteger along with a few other members. I override the - (NSString *)description method to print the values of everything like so (omitted the unrelevant part):
- (NSString *)description {
return [NSString stringWithFormat:#"Duration:%d", duration];
}
and then I print that using NSLog(#"%#", myObject) which is giving me EXC_BAD_ACCESS without any log messages, regardless of the NSZombieEnabled.
I have double checked the order of all formatting specifiers and
parameters - it's correct.
I tried changing the format specifier to %i and %# and didn't get any result
When I init my object I don't initialize
duration. Later, I assign it through a property #property NSInteger
duration. So I tried initializing the duration to 0 in my init
method but to no avail.
If I box duration to a NSNumber prior to
printing, it works.
When I remove the duration and leave all the
other ivars it works again.
I'm stumped, what am I doing wrong here?
Can you help me?
Thanks a lot!
EDIT: To summarize, It seems this is caused by differences between 32 and 64 bit platforms, because it's fine when run on an iphone 4 and has issues only when run in the simulator. I tried 3 different approaches - using %d and %i with the NSInteger variable itself, using %qi and using %ld/ %lx and I also tried to cast to int and long with the various format specifiers. Every time I can run on the device, but get EXC_BAD_ACCESS in the simulator.
The only guess here: NSInteger could be 64 bit in your case and %i (as well as %d) expects 32-bit integer, you can try %qi or (what seems to be better) cast the value explicitly.
When you run the app in the debugger, where exactly is the signal raised? Xcode will show you the precise line and stack of the problem.
When you are sure the crash happens in the stringWithFormat: method it's probably a matter of format specifiers. Apple's String Programming Guide contains information on how to handle NSInteger in a safe and platform independent way.
Maybe you need to synthesyse your property?

Basic problems (type inference or something else?) in Objective-C/Cocoa

Apologies for how basic these questions are to some. Just started learning Cocoa, working through Hillegass' book, and am trying to write my first program (a GUI Cocoa app that counts the number of characters in a string).
I tried this:
NSString *string = [textField stringValue];
NSUInteger *stringLength = [string length];
NSString *countString = (#"There are %u characters",stringLength);
[label setStringValue:countString];
But I'm getting errors like:
Incompatible pointer conversion initializing 'NSUInteger' (aka 'unsigned long'), expected 'NSUInteger *'[-pedantic]
for the first line, and this for the second line:
Incompatible pointer types initializing 'NSUInteger *', expected 'NSString *' [-pedantic]
I did try this first, but it didn't work either:
[label setStringValue:[NSString stringWithFormat:#"There are %u characters",[[textField stringValue] length]]]
On a similar note, I've only written in easy scripting languages before now, and I'm not sure when I should be allocing/initing objects and when I shouldn't.
For example, when is it okay to do this:
NSString *myString = #"foo";
or
int *length = 5;
instead of this:
NSString *myString = [[NSString alloc] initWithString:"foo"];
And which ones should I be putting into the header files?
I did check Apple's documentation, and CocoaDev, and the book I'm working for but without luck. Maybe I'm looking in the wrong places..
Thanks to anyone who takes the time to reply this: it's appreciated, and thanks for being patient with a beginner. We all start somewhere.
EDIT
Okay, I tried the following again:
[label setStringValue:[NSString stringWithFormat:#"There are %u characters",[[textField stringValue] length]]]
And it actually worked this time. Not sure why it didn't the first time, though I think I might have typed %d instead of %u by mistake.
However I still don't understand why the code I posted at the top of my original post doesn't work, and I have no idea what the errors mean, and I'd very much like to know because it seems like I'm missing something important there.
The first problem is the fact that -[NSString length] returns an NSUInteger, not an NSUInteger *. The first is an integer; the second is a pointer to an integer (more on that in a second). The second problem is that this line:
NSString *countString = (#"There are %u characters",stringLength);
Doesn't do what you expect. I think you want to format a string, but what this is actually doing is executing the expression #"There are %u characters", then stringLength, and returning the value of the second expression. What you want is this:
NSString *countString = [NSString stringWithFormat:#"There are %u characters", stringLength];
Which you said didn't work, but I'm not sure why.
For example, when is it okay to do this ... instead of this:
It's "okay" when you're setting a variable of that type to return value of the same type. In C (and Objective-C is just some stuff on top of C), there are values and then there are pointers to values. Pointers to values are actually memory addresses that point to a value in memory. For example, this is a value of type int:
int x = 5;
And this is a pointer to that value:
int *xptr = &x;
x has the value 5. xptr has some memory address as its value. *xptr (the dereferenced value of xptr) has a value of 5, since xptr points to a value of 5 in memory.
This is not something you have to worry about much in Cocoa. The API docs will tell you the return values of methods and functions, and Objective-C objects are always pointers. There are a few types to watch out for, though: NSRect, NSPoint, and some other values are just C structs (not Objective-C objects) and thus sometimes you deal with the values themselves, and not pointers. And things like NSUInteger are generally not pointers, either (but sometimes they are).
(For some reason I can't edit my original post.)
Okay, I tried the following again:
[label setStringValue:[NSString stringWithFormat:#"There are %u characters",[[textField stringValue] length]]]
And it actually worked this time. Not sure why it didn't the first time, though I think I might have typed %d instead of %u by mistake.
However I still don't understand why the code I posted at the top of my original post doesn't work, and I have no idea what the errors mean, and I'd very much like to know because it seems like I'm missing something important there.

Warning: "format not a string literal and no format arguments"

Since upgrading to the latest Xcode 3.2.1 and Snow Leopard, I've been getting the warning
"format not a string literal and no format arguments"
from the following code:
NSError *error = nil;
if (![self.managedObjectContext save:&error])
{
NSLog([NSString stringWithFormat:#"%# %#, %#",
errorMsgFormat,
error,
[error userInfo]]);
}
If errorMsgFormat is an NSString with format specifiers (eg: "print me like this: %#"), what is wrong with the above NSLog call? And what is the recommended way to fix it so that the warning isn't generated?
Xcode is complaining because this is a security problem.
Here's code similar to yours:
NSString *nameFormat = #"%# %#";
NSString *firstName = #"Jon";
NSString *lastName = #"Hess %#";
NSString *name = [NSString stringWithFormat:nameFormat, firstName, lastName];
NSLog(name);
That last NSLog statement is going to be executing the equivalent of this:
NSLog(#"Jon Hess %#");
That's going to cause NSLog to look for one more string argument, but there isn't one. Because of the way the C language works, it's going to pick up some random garbage pointer from the stack and try to treat it like an NSString. This will most likely crash your program. Now your strings probably don't have %#'s in them, but some day they might. You should always use a format string with data you explicitly control as the first argument to functions that take format strings (printf, scanf, NSLog, -[NSString stringWithFormat:], ...).
As Otto points out, you should probably just do something like:
NSLog(errorMsgFormat, error, [error userInfo]);
Are you nesting your brackets correctly? I don't think NSLog() likes taking only one argument, which is what you're passing it. Also, it already does the formatting for you. Why not just do this?
NSLog(#"%# %#, %#",
errorMsgFormat,
error,
[error userInfo]);
Or, since you say errorMsgFormat is a format string with a single placeholder, are you trying to do this?
NSLog(#"%#, %#", [NSString stringWithFormat:errorMsgFormat, error],
[error userInfo]);
Final answer: As Jon Hess said, it's a security issue because you're passing a WHATEVER string to a function expecting a format string. That is, it'll evaluate all format specifiers WITHIN the whatever string. If there aren't any, awesome, but if there are, bad things could happen.
The proper thing to do, then, is USE a format string directly, for example
NSLog(#"%#", myNSString);
That way, even if there are format specifiers in myNSString, they don't get evaluated by NSLog.
I don't especially recommend using this, since the warning IS a real warning.. in a dynamic use of the language it's possible to do things runtime to the string (i.e. insert new information or even crash the program).. However it's possible to force suppress if you KNOW that it should be like this and you really don't want to be warned about it..
#pragma GCC diagnostic ignored "-Wformat-security"
Would tell GCC to temporarily ignore the compilation warning.. Again it's not solving anything but there may be times when you can't find a good way to actually fix the problem.
EDIT: As of clang, the pragma has changed. See this: https://stackoverflow.com/a/17322337/3937
Quickest way to fix it would be to add #"%#", as the first argument to your NSLog call, i.e.,
NSLog(#"%#", [NSString stringWithFormat: ....]);
Though, you should probably consider Sixteen Otto's answer.
I've just been passing a nil to negate the warnings, maybe that would work for you?
NSLog(myString, nil);
If you want get rid of the warning "format not a string literal and no format arguments" once and for all, you can disable the GCC warning setting "Typecheck Calls to printf/scanf" (GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = NO) in your target's build settings.
NSLog() expects a format string, what is getting passed in is just a string. You do not need to use stringWithFormat:, you can just do:
NSLog(#"%# %#, %#", errorMsgFormat, error, [error userInfo])
And that would make the warning go away.
FWIW, this applies to iPhone dev as well. I'm coding against the 3.1.3 SDK, and got the same error with the same problem (nesting stringWithFormat inside NSLog()). Sixten and Jon are on the money.
Just letting anyone know using the appendFormat on NSMutableString can also cause this warning to appear if trying to pass in a formatted string like so:
NSMutableString *csv = [NSMutableString stringWithString:#""];
NSString *csvAddition = [NSString stringWithFormat:#"%#",WHATEVERYOUAREPUTTINGINYOURSTRING];
[csv appendFormat:csvAddition];
So to avoid this warning, turn the above into this:
NSMutableString *csv = [NSMutableString stringWithString:#""];
[csv appendFormat:#"%#",WHATEVERYOUAREPUTTINGINYOURSTRING];
More concise and more secure. Enjoy!
NSLog(#"%# %#, %#",
errorMsgFormat,
error,
[error userInfo]);