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]);
Related
What should be the correct format of the below to print *newString ?
NSString *newString = #"Hello this is a string!";
NSLog(#newString);
NSLog works pretty much as a C printf, with the addition of the %# string format specifier, which is meant for objects. Being NSString an object, %# is the right format to use:
NSString *newString = #"Hello this is a string!";
NSLog(#"%#", newString);
For as tempting as it can look, NEVER do
NSLog(newString); //NONONONONO!
since it's a terrible practice that may lead to unexpected crashes (not to mention security issues).
More info on the subject: Warning: "format not a string literal and no format arguments"
The # symbol is just a shorthand for specifying some common Objective-C objects. #"..." represents a string (NSString to be specific, which is different from regular C strings), #[...] represents an array (NSArray), #{...} represents a dictionary (NSDictionary).
On the first line, you've already specified a NSString object using the # sign. newString is now an NSString instance. On the second line, you can just give it's variable name:
NSLog(newString);
You could theoretically just give the variable name, but it is a dangerous approach. If newString has any format specifiers, your app may crash/mess up (or access something that it shouldn't be accesing) because NSLog would try to read the arguments corresponding to the format specifiers, but the arguments don't exist. The safe solution would be NSLog(#"%#", newString);. The first argument to NSLog is now hard-coded and can't be changed. We now know that it will expect a single argument, that we are providing that argument, newString, so we are safe.
Because you've already specified a string and just passing that instance to NSLog, you don't need the # sign again.
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.
I have a Mac application that keeps it's own log file. It appends info to the file using NSString's writeToFile method. One of the things that it logs are URL's of web services that it is interacting with. To encode the URL, I'm doing this:
searchString = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)searchString, NULL, (CFStringRef)#"!*'();:#&=+$,/?%#[]", kCFStringEncodingUTF8 );
The app then appends searchString to the rest of the URL and writes it to the log file. Now the problem is that after adding that URL encoding line, nothing seems to be getting written to the file. The program functions as expected otherwise however. Removing the line of code above results in all of the correct information being logged to the file (removing that line is not an option because searchString must be URL encoded).
Oh and I am using NSUTF8StringEncoding when writing the NSString to the file.
Thanks for any help.
EDIT: I know there's also a similar function to CFURLCreateStringByAddingPercentEscapes in NSString, but I've read that it doesn't always work. Can anyone shed some light on this if my original question cannot be answered? Thanks! (EDIT: same problem occurs when using stringByAddingPercentEscapesUsingEncoding:)
EDIT 2: Here's the code that I'm using to append messages to the log file.
+(void)logText:(NSString *)theString{
NSString *docsDirectory = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory,NSUserDomainMask,YES) objectAtIndex:0];
NSString *path = [docsDirectory stringByAppendingPathComponent:#"Folder/File.log"];
NSString *fileContents = [[[NSString alloc] initWithContentsOfFile:path] autorelease];
if([fileContents lengthOfBytesUsingEncoding:NSUTF8StringEncoding] >= 204800){
fileContents = #"";
}
NSString *timeStamp = [[NSDate date] description];
timeStamp = [timeStamp stringByAppendingString:#": "];
timeStamp = [timeStamp stringByAppendingString:theString];
fileContents = [fileContents stringByAppendingString:timeStamp];
fileContents = [fileContents stringByAppendingString:#"\n"];
[fileContents writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:NULL];
}
Because after almost a whole day no one else has offered any answers, I'm going to post a wild guess here: you're not accidentally using the string you want to output (with percent characters in it) as a format string are you?
That is, making the mistake of doing:
NSLog(#"In format strings you can use %# as a placeholder for an object, and %i for a plain C integer.")
Instead of:
NSLog(#"%#", #"In format strings you can use %# as a placeholder for an object, and %i for a plain C integer.");
But I'm going to be surprised if this turns out to be the cause of your problem, as it usually causes random-looking output, rather than absolutely no output. And in some cases, Xcode also gives compiler warnings about it (when I tried NSLog(myString), I got "warning: format not a string literal and no format arguments").
So don't shoot me down if this answer doesn't help. It would be easier to answer your question if you could show us more of your logging code. As for the one line you provided, I can't detect anything wrong with it.
Edit: Oops, I kind of missed that you mentioned you're using writeToFile:atomically:encoding:error: to write the string to the file, so it's even more unlikely you're accidentally treating it as a format string somewhere. But I'm going to leave this answer up for now. Again, you should really show us more of your code though ...
Edit: Regarding your question on a method in NSString that has similar percent encoding functionality, that would be stringByAddingPercentEscapesUsingEncoding:. I'm not sure what kind of problems you're thinking of when you say you've heard it doesn't always work. But one thing is that CFURLCreateStringByAddingPercentEscapes allows you to specify extra characters that don't normally have to be escaped but which you still want to be escaped, while the method of NSString doesn't allow you to specify this.
I want to print out the Text contents of a NSTextView using the NSLog function in Objective-C. The code I have so far is:
NSString *s=[updateSource textStorage];
NSLog(s);
All I get is the error:
[NSConcreteTextStorage getCharacters:range:]: selector not recognized [self = 0x43f4b0]
Use [updateSource string] instead. [updateSource textStorage] is not an NSString, but rather an NSTextStorage.
It's not the cause of your problem, but you should be using NSLog(#"%#",s); to log your string. The first argument of NSLog should always be a format string, and not the value you're trying to log.
(if you don't, your app will likely crash if the value contains percent characters)
This one is weird. Hopefully I will ask the right question:
I'm using an md5 method to create a checksum value which I then write to a file. Then afterwards I read the file using this:
NSString * id_From_File = [[NSString alloc]
initWithContentsOfFile:path_to_ID
encoding:NSUTF8StringEncoding
error:&error];
The result gets placed in a NSString which when I print gives me very strange behaviour. For example when I use this to print,
id_with_date = [NSString stringWithFormat:#" %# %#", dateString, id_From_File];
it will print both strings if dateString is placed in the first parameter and id_From_File in the second. If I switch them around (which I need to do) only id_From_File shows.
Edit 1: Example of the switch:
id_with_date = [NSString stringWithFormat:#" %# %#", id_From_File, dateString];
I strongly believe this has something to do with the encoding of the id_From_File string.
Any knowledge!?
Thanks,
NSString should actually be capable of recognizing null characters as the file ending. Did you try to use a different method to load the string. I'd go for this one instead:
- (id)initWithContentsOfFile:(NSString *)path usedEncoding:(NSStringEncoding *)enc error:(NSError **)error
This method automatically detects the file's encoding instead of decoding it with a fixed one.
I've solved the problem!
It has to do with the fact that some strings use a null character to identify the end. Allow me to explain:
Lets say you have two strings, one with a null character at the end and one that doesn't. Depending on which way you order them, they will be read differently when concatenated.
"somestring(null char)" + "another string"
The above, in some code, will read
somestring
if places are switched
"another string" + "somestring(null char)"
then you get
"another string somestring"
My simple hack to fix this was to make a new string with a substring of "some string" which easily got rid of that last char that was causing the bug.
I hope this is clear and helpful!