Aviso: “formato não é uma string literal e sem argumentos de formato”

110

Desde a atualização para o Xcode 3.2.1 e Snow Leopard mais recente, tenho recebido o aviso

"formato não é uma string literal e sem argumentos de formato"

do seguinte código:

NSError *error = nil;

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

}

Se errorMsgFormatfor um NSStringcom especificadores de formato (por exemplo "print me like this: %@":), o que há de errado com a NSLogchamada acima ? E qual é a forma recomendada de corrigir isso para que o aviso não seja gerado?

Alexi Groove
fonte

Respostas:

113

Você está aninhando seus colchetes corretamente? Eu não acho que NSLog()goste de aceitar apenas um argumento, que é o que você está passando. Além disso, ele já faz a formatação para você. Por que não fazer isso?

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

Ou, já que você diz que errorMsgFormaté uma string de formato com um único espaço reservado, você está tentando fazer isso?

NSLog(@"%@, %@", [NSString stringWithFormat:errorMsgFormat, error], 
   [error userInfo]);              
Sixten Otto
fonte
14
"Não acho que NSLog () goste de aceitar apenas um argumento" NSLog()pode aceitar um argumento, quando a string de formato não contém especificadores de formato.
user102008
Dá outro aviso Argumento de dados não usado pela string de formato.
hasan
157

O Xcode está reclamando porque isso é um problema de segurança.

Este é um código semelhante ao seu:

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

Essa última instrução NSLog executará o equivalente a isto:

NSLog(@"Jon Hess %@");

Isso fará com que o NSLog procure mais um argumento de string, mas não há nenhum. Por causa da maneira como a linguagem C funciona, ela pegará algum ponteiro de lixo aleatório da pilha e tentará tratá-lo como um NSString. Isso provavelmente irá travar seu programa. Agora, suas strings provavelmente não têm% @'s nelas, mas algum dia poderão. Você deve sempre usar uma string de formato com dados que você controla explicitamente como o primeiro argumento para funções que usam strings de formato (printf, scanf, NSLog, - [NSString stringWithFormat:], ...).

Como Otto aponta, você provavelmente deve apenas fazer algo como:

NSLog(errorMsgFormat, error, [error userInfo]);
Jon Hess
fonte
17
E mais uma vez no SO, as respostas detalhadas e boas caem no esquecimento. OBRIGADO por explicar isso completamente. Eu nunca teria descoberto isso.
Dan Rosenstark
38

Resposta final: Como Jon Hess disse, é um problema de segurança porque você está passando uma string DE QUALQUER COISA para uma função que espera uma string de formato. Ou seja, ele avaliará todos os especificadores de formato DENTRO de qualquer string. Se não houver nenhum, ótimo, mas se houver, coisas ruins podem acontecer.

A coisa certa a fazer, então, é USE uma string de formato diretamente, por exemplo

NSLog(@"%@", myNSString);

Dessa forma, mesmo se houver especificadores de formato em myNSString, eles não serão avaliados pelo NSLog.

Alex Whittemore
fonte
13

Eu não recomendo especialmente usar isso, já que o aviso É um aviso real .. em um uso dinâmico da linguagem é possível fazer coisas em tempo de execução para a string (ou seja, inserir novas informações ou mesmo travar o programa). No entanto, é possível forçar a supressão se você SABE que deveria ser assim e você realmente não quer ser avisado sobre isso ..

#pragma GCC diagnostic ignored "-Wformat-security"

Diria ao GCC para ignorar temporariamente o aviso de compilação. Novamente, não está resolvendo nada, mas pode haver momentos em que você não consegue encontrar uma boa maneira de realmente consertar o problema.

EDIT: A partir do clang, o pragma mudou. Veja isto: https://stackoverflow.com/a/17322337/3937

Qrikko
fonte
10

A maneira mais rápida de corrigir isso seria adicionar @"%@",o primeiro argumento à sua NSLogchamada, ou seja,

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

Porém, você provavelmente deveria considerar a resposta de Dezesseis Otto.

Anthony Cramp
fonte
10

Acabei de passar um zero para negar os avisos, talvez isso funcione para você?

NSLog (minhaString, nulo);

Martytoof
fonte
5
Alguém pode explicar POR QUE passar nil como segundo paramente resolve o aviso?
cprcrack
1
Passar nil é explícito, enquanto a falta de um segundo parâmetro não é. Você pode presumir que sua lareira não estava acesa quando saiu de casa ou pode ter certeza de que não estava. Embora normalmente nada aconteça porque você raramente usa sua lareira, será naquele momento em que sua casa pegará fogo.
1
@SoldOutActivist Inútil. O ponto não óbvio aqui (para alguém que não vem de um background C) é qual é a diferença de comportamento entre passar um nil explícito e não passar nada, e seu comentário não explica isso.
Mark Amery
Ótimo: Qualquer método Obj-C que pode aceitar um número variável de argumentos deve ser explicitamente terminado em nil. Não passar nada não é o mesmo que passar nulo. Passe qualquer tempo com Obj-C e você verá isso repetidamente. A construção de matrizes é a mais comum.
3
Isso pode parar o aviso do compilador, mas o problema subjacente, que foi explicado por Jon Hess , ainda existe - se houver mais de um especificador de formato myString, o primeiro ficará bem, mas o segundo pegará o lixo da pilha. A lista de substituição em nuncaNSLog() é terminada, @Sold. Existem duas opções para descobrir o tamanho da lista de argumentos: um valor sentinela ou o que é usado em uma família - outro argumento que permite o cálculo do número (por exemplo, contando os especificadores de formato). nilprintf()
jscs
3

Se você quiser se livrar do aviso "formato não é uma string literal e sem argumentos de formato" de uma vez por todas, você pode desativar a configuração de aviso do GCC "Typecheck Calls to printf / scanf" (GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = NO) nas configurações de compilação do seu destino.

Aldi
fonte
5
Isso silenciará o aviso, mas não fará nada para corrigir a falha subjacente em seu aplicativo. Ao silenciar o aviso, você está ignorando um bug em potencial que pode travar seu aplicativo com base simplesmente nos dados inseridos pelo usuário (ou, neste caso, a mensagem de erro gerada pelo CoreData). Seria melhor seguir algumas das outras respostas nesta pergunta para remover o bug do código-fonte que está causando o aparecimento do aviso.
Christopher Fairbairn
2
Verdade ... É por isso que postei "livrar-se do aviso" em vez de "resolver".
aldi
Eu encontrei um caso em que a biblioteca uthash estava disparando esse aviso em chamadas para sua função utstring_printf, então isso é útil em situações em que o aviso está errado.
alfwatt
2

NSLog () espera uma string de formato, o que está sendo passado é apenas uma string. Você não precisa usar stringWithFormat :, você pode apenas fazer:

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

E isso faria o aviso desaparecer.

Elfred
fonte
2

FWIW, isso se aplica ao iPhone dev também. Estou codificando com o SDK 3.1.3 e obtive o mesmo erro com o mesmo problema (aninhando stringWithFormat dentro de NSLog ()). Sixten e Jon estão no dinheiro.

Pettiross
fonte
0

Apenas avisar a alguém usando appendFormatNSMutableString também pode fazer com que este aviso apareça se tentar passar uma string formatada como esta:

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

Portanto, para evitar esse aviso, transforme o acima em:

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

Mais conciso e mais seguro. Aproveitar!

ColossalChris
fonte
-2
NSLog(@"%@ %@, %@", 
       errorMsgFormat, 
       error, 
       [error userInfo]); 
ILYA2606
fonte
1
Usar stringWithFormaté redundante aqui quando você poderia apenas fazerNSLog(@"%@ %@, %@", errorMsgFormat, error, [error userInfo])
Mark Amery,