Предупреждение: «формат не строковый литерал и аргументы формата»

110

После обновления до последней версии Xcode 3.2.1 и Snow Leopard я получаю предупреждение

"форматировать не строковый литерал и не аргументы формата"

из следующего кода:

NSError *error = nil;

if (![self.managedObjectContext save:&error]) 
{
    NSLog([NSString stringWithFormat:@"%@ %@, %@", 
       errorMsgFormat, 
       error, 
       [error userInfo]]);      

}

Если errorMsgFormatэто NSStringспецификаторы формата with (например:) "print me like this: %@", что не так с приведенным выше NSLogвызовом? И как это рекомендуется исправить, чтобы предупреждение не генерировалось?

Алекси Грув
источник

Ответы:

113

Правильно ли вы вставляете скобки? Не думаю, что NSLog()нравится приводить только один аргумент, а именно то, что вы его передаете. Кроме того, он уже выполняет форматирование за вас. Почему бы просто не сделать это?

NSLog(@"%@ %@, %@", 
   errorMsgFormat, 
   error, 
   [error userInfo]);              

Или, поскольку вы говорите, errorMsgFormatчто это строка формата с одним заполнителем, вы пытаетесь это сделать?

NSLog(@"%@, %@", [NSString stringWithFormat:errorMsgFormat, error], 
   [error userInfo]);              
Сикстен Отто
источник
14
«Я не думаю, что NSLog () любит принимать только один аргумент» NSLog()может принимать один аргумент, когда строка формата не содержит спецификаторов формата.
user102008
Выдает еще одно предупреждение. Аргумент данных не используется строкой формата.
hasan
157

Xcode жалуется, потому что это проблема безопасности.

Вот код, похожий на ваш:

NSString *nameFormat = @"%@ %@";
NSString *firstName = @"Jon";
NSString *lastName = @"Hess %@";
NSString *name = [NSString stringWithFormat:nameFormat, firstName, lastName];
NSLog(name);

Последний оператор NSLog будет выполнять эквивалент этого:

NSLog(@"Jon Hess %@");

Это заставит NSLog искать еще один строковый аргумент, но его нет. Из-за того, как работает язык C, он собирает случайный указатель мусора из стека и пытается рассматривать его как NSString. Скорее всего, это приведет к сбою вашей программы. Теперь в ваших строках, вероятно, нет% @, но когда-нибудь они могут быть. Вы всегда должны использовать строку формата с данными, которыми вы явно управляете, в качестве первого аргумента для функций, которые принимают строки формата (printf, scanf, NSLog, - [NSString stringWithFormat:], ...).

Как указывает Отто, вам, вероятно, следует просто сделать что-то вроде:

NSLog(errorMsgFormat, error, [error userInfo]);
Джон Хесс
источник
17
И снова на SO подробные и хорошие ответы остаются на второй план. СПАСИБО за подробные объяснения. Я бы никогда этого не понял.
Дэн Розенстарк,
38

Окончательный ответ: как сказал Джон Хесс, это проблема безопасности, потому что вы передаете строку WHATEVER функции, ожидающей строку формата. То есть он будет оценивать все спецификаторы формата ВНУТРИ строки any. Если их нет - круто, но если есть, могут случиться плохие вещи.

В таком случае правильным будет ИСПОЛЬЗОВАТЬ строку формата напрямую, например

NSLog(@"%@", myNSString);

Таким образом, даже если в myNSString есть спецификаторы формата, они не будут оцениваться NSLog.

Алекс Виттемор
источник
13

Я особенно не рекомендую использовать это, поскольку предупреждение ЯВЛЯЕТСЯ настоящим предупреждением .. при динамическом использовании языка можно выполнять действия со строкой (т.е. вставлять новую информацию или даже сбой программы). Однако это возможно принудительно подавить, если вы ЗНАЕТЕ, что это должно быть так, и вы действительно не хотите, чтобы вас об этом предупреждали ..

#pragma GCC diagnostic ignored "-Wformat-security"

Сказал бы GCC временно игнорировать предупреждение о компиляции. Опять же, это ничего не решает, но могут быть моменты, когда вы не можете найти хороший способ решить проблему.

РЕДАКТИРОВАТЬ: Начиная с clang, прагма изменилась. Смотрите это: https://stackoverflow.com/a/17322337/3937

Qrikko
источник
10

Самый быстрый способ исправить это - добавить @"%@",в качестве первого аргумента вашего NSLogвызова, т. Е.

NSLog(@"%@", [NSString stringWithFormat: ....]);

Хотя вам, вероятно, стоит подумать об ответе Шестнадцати Отто.

Энтони Крэмп
источник
10

Я только что пропустил ноль, чтобы опровергнуть предупреждения, может быть, это сработает для вас?

NSLog (myString, ноль);

Martytoof
источник
5
Может ли кто-нибудь объяснить, ПОЧЕМУ передача nil в качестве второго параметра решает предупреждение?
cprcrack 03
1
Передача nil является явной, а отсутствие второго параметра - нет. Вы можете предположить, что ваш камин не горел, когда вы выходили из дома, или можете убедиться, что это не так. Хотя обычно ничего не происходит из-за того, что вы редко используете свой камин, это будет именно тот случай, когда ваш дом сгорит.
1
@SoldOutActivist Бесполезно. Неочевидный момент здесь (для тех, кто не имеет опыта работы с C) - это разница в поведении между передачей явного nil и отсутствием передачи, и ваш комментарий этого не объясняет.
Марк Эмери
Хорошо: все методы Obj-C, которые могут принимать переменное количество аргументов, должны явно оканчиваться нулем. Ничего не передать - это не то же самое, что передать ноль. Проведите любое время с Obj-C, и вы будете видеть это снова и снова. Строительные массивы являются наиболее распространенными.
3
Это может остановить предупреждение компилятора, но основная проблема, которую объяснил Джон Хесс , все еще существует - если есть более одного спецификатора формата myString, с первым будет все в порядке, но второй будет собирать мусор из стека. Список подмены NSLog()не никогда nil -завершённый, @Sold. Есть два варианта определения длины списка аргументов: контрольное значение или то, что используется в printf()и семейство - еще один аргумент, который позволяет вычислить число (например, путем подсчета спецификаторов формата).
jscs 08
3

Если вы хотите избавиться от предупреждения «формат, а не строковый литерал и без аргументов формата» раз и навсегда, вы можете отключить параметр предупреждения GCC «Typecheck Calls to printf / scanf» (GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = NO) в настройках вашей целевой сборки.

Альди
источник
5
Это отключит предупреждение, но ничего не сделает для исправления основной ошибки в вашем приложении. Отключив предупреждение, вы игнорируете потенциальную ошибку, которая может привести к сбою вашего приложения на основе данных, введенных пользователем (или в данном случае сообщения об ошибке, созданного CoreData). Было бы лучше следовать некоторым другим ответам в этом вопросе, чтобы удалить ошибку в исходном коде, которая вызывает появление предупреждения.
Кристофер Фэйрбэрн,
2
Верно ... Поэтому я написал «избавиться от предупреждения» вместо «решить».
aldi
Я столкнулся со случаем, когда библиотека uthash запускала это предупреждение при вызове функции utstring_printf, поэтому это полезно в ситуациях, когда предупреждение неверно.
alfwatt
2

NSLog () ожидает строку формата, а передается просто строка. Вам не нужно использовать stringWithFormat :, вы можете просто:

NSLog(@"%@ %@, %@", errorMsgFormat, error, [error userInfo])

И тогда предупреждение исчезнет.

Эльфред
источник
2

FWIW, это относится и к разработчикам iPhone. Я кодирую SDK 3.1.3 и получил ту же ошибку с той же проблемой (вложение stringWithFormat внутри NSLog ()). Сикстен и Джон в деньгах.

Петтиросс
источник
0

Если просто appendFormatсообщить кому-либо об использовании в NSMutableString, это может также вызвать появление этого предупреждения при попытке передать отформатированную строку следующим образом:

NSMutableString *csv = [NSMutableString stringWithString:@""];
NSString *csvAddition = [NSString stringWithFormat:@"%@",WHATEVERYOUAREPUTTINGINYOURSTRING];
[csv appendFormat:csvAddition];

Чтобы избежать этого предупреждения, превратите приведенное выше в следующее:

NSMutableString *csv = [NSMutableString stringWithString:@""];
[csv appendFormat:@"%@",WHATEVERYOUAREPUTTINGINYOURSTRING];

Более кратко и надежно. Наслаждайтесь!

ColossalChris
источник
-2
NSLog(@"%@ %@, %@", 
       errorMsgFormat, 
       error, 
       [error userInfo]); 
ILYA2606
источник
1
Использование stringWithFormatздесь излишне, когда вы могли бы просто сделатьNSLog(@"%@ %@, %@", errorMsgFormat, error, [error userInfo])
Марк Эмери