【问题标题】:Objective-C: "format string is not a string literal (potentially insecure)" warning with macroObjective-C:“格式字符串不是字符串文字(可能不安全)”宏警告
【发布时间】:2013-08-29 05:46:45
【问题描述】:

我正在使用宏来简化返回本地化字符串,如下所示:

#define GetLocalStr(key, ...) \
    [NSString stringWithFormat:[[NSBundle mainBundle] localizedStringForKey:key value:@"" table:nil], ##__VA_ARGS__]

基本上,如果您在本地化字符串文件中有一个条目,例如 "name" = "My name is %@";,则调用

GetLocalStr( @"name", @"Foo" );

将返回 NSString @"My name is Foo"

但是,当我运行它时,例如:

NSString * str = GetLocalStr( @"name", @"Foo" );

我收到“格式字符串不是字符串文字”警告。即使遵循关于此警告的其他答案的建议并将其替换为:

NSString * str = [NSString stringWithFormat:@"%@", GetLocalStr( @"name", @"Foo" )];

我仍然收到警告,此外,它有点违背了让生活更轻松的宏的意义。

如果没有将所有GetLocalStr 调用包装在#pragma 抑制器中,我该如何摆脱警告?

编辑 27/08

在运行 CRD 的答案并进行更多测试后,我似乎对错误做出了错误的假设。澄清一下:

本地化字符串文件:

"TestNoArgs" = "Hello world";
"TestArgs" = "Hello world %@";

代码:

NSString * str1 = GetLocalStr( @"TestNoArgs" ); // gives warning
NSString * str2 = GetLocalStr( @"TestArgs", @"Foo" ); // doesn't give warning

我的大部分翻译都没有任何参数,并且是那些发出警告的翻译,但直到我阅读了 CRD 的答案,我才建立联系。

我将单个宏更改为两个,如下所示:

#define GetLocalStrNoArgs(key) \
    [[NSBundle mainBundle] localizedStringForKey:key value:@"" table:nil]

#define GetLocalStrArgs(key, ...) \
    [NSString stringWithFormat:[[NSBundle mainBundle] localizedStringForKey:key value:@"" table:nil], ##__VA_ARGS__]

如果我分别调用每一个,则不会有任何警告。

我希望 GetLocalStr 扩展到 GetLocalStrNoArgsGetLocalStrArgs 取决于是否传递了任何参数,但到目前为止我一直没有运气(宏不是我的强项:D )。

我正在使用sizeof(#__VA_ARGS__) 来确定是否传递了任何参数 - 它对参数进行字符串化,如果大小为 1,则为空(即 `\0')。也许这不是最理想的方法,但它似乎有效。

如果我将 GetLocalStr 宏重写为:

#define GetLocalStr(key,...) (sizeof(#__VA_ARGS__) == 1) ? GetLocalStrNoArgs(key) : GetLocalStrArgs(key,##__VA_ARGS__)

我可以使用它,但我仍然在使用它的任何地方都会收到警告,并且没有传递任何参数,而类似于

#define GetLocalStr( key,...)               \
    #if ( sizeof(#__VA_ARGS__) == 1 )       \
        GetLocalStrNoArgs(key)              \
    #else                                   \
        GetLocalStrArgs(key,##__VA_ARGS__)

不会编译。如何让我的GetLocalStr 宏正确展开?

【问题讨论】:

  • @"name" = @"My name is %@"; - 文字不可赋值,它不会编译。 NSString * format = @"My name is %@"; NSString * str = GetLocalStr( format, @"Foo" ); NSLog(@"%@", str); 不会引发任何警告(无论如何,我希望它只出现在 GetLocalStr 定义中),您可能需要修复引号并提供有关警告出现位置的更多详细信息。
  • 你可以创建一个 NSString 类别来为你做这件事。
  • @A-Live @"name" = @"My name is %@" 行位于字符串本地化文件中,所以从技术上讲,它应该只是 "name" = "My name is %@" - 抱歉,如果不清楚。警告标记在NSString * str = GetLocalStr(...);
  • @JoshCaswell 我知道有关此警告的其他问题,但我找不到任何对我有帮助的东西。如果您正在查看特定答案,我将不胜感激
  • 好的,我用一行"name" = "my name is %@"; 创建了Localizable.strings,你的代码仍然没有警告。

标签: ios objective-c string macros


【解决方案1】:

Clang 和 GCC 编译器检查格式字符串和提供的参数是否一致,如果格式字符串不是文字,它们就不能这样做 - 因此当您从包中获取格式字符串时会看到错误消息。

为了解决这个问题,有一个属性format_arg(n) (docs) 来标记采用格式字符串的函数;以某种方式改变它而不改变实际的格式说明符,例如翻译它;然后返回它。 Cocoa 为这个属性提供了方便的宏NS_FORMAT_ARG(n)

要解决您的问题,您需要做两件事:

  1. 在指定此属性的函数中结束对NSBundle 的调用;和

  2. 更改“键”以包含格式说明符。

其次,你的字符串文件应该包含:

"name %@" = "My name is %@"

因此键具有与结果相同的格式说明符(如果您需要为特定语言重新排序说明符,则使用位置格式说明符)。

现在定义一个简单的函数来进行查找,将其归为格式转换函数。请注意,我们将其标记为static inline,使用宏NS_INLINE 作为编译器将其内联到宏扩展中的提示; static 允许您将其包含在多个文件中而不会发生符号冲突:

NS_INLINE NSString *localize(NSString *string) NS_FORMAT_ARGUMENT(1);
NSString *localize(NSString *string)
{
   return [[NSBundle mainBundle] localizedStringForKey:string value:@"" table:nil];
}

你的宏变成:

#define GetLocalStr(key, ...) [NSString stringWithFormat:localize(key), ##__VA_ARGS__]

现在当你:

GetLocalStr(@"name %@", @"Foo")

您将获得本地化格式字符串和格式检查。

更新

在 Greg 发表评论后,我回去检查了 - 我已经重现了您的错误,因此 假设 它归结为缺少属性。然而,正如 Greg 指出的那样 localizedStringForKey:value:table: 已经具有该属性,那么为什么会出现错误?我在重现您的错误时心不在焉地做的是:

NSLog( GetLocalStr( @"name %@", @"Foo" ) );

并且编译器指向 macro 定义而不是那行 - 我应该发现编译器误导了我。

那么这会让你在哪里?也许你做过类似的事情?关键是格式字符串必须是文字或作为格式转换函数的函数/方法的结果。并且不要忘记,您的密钥还必须具有上述格式说明符。

更新 2

在您的附加 cmets 之后,您需要使用的是函数,而不是宏,以及 format 属性,Cocoa 为其提供了方便的 NS_FORMAT_FUNCTION(f,a) 宏。该属性通知编译器该函数是一个格式化函数,f 的值是格式字符串的编号,a 是格式的第一个参数的编号。这给出了函数声明:

NSString *GetLocalStr(NSString *key, ...) NS_FORMAT_FUNCTION(1,2);

以及定义(假设为 ARC):

NSString *GetLocalStr(NSString *key, ...)
{
   va_list args;
   va_start(args, key);
   NSString *format = [[NSBundle mainBundle] localizedStringForKey:key value:@"" table:nil];
   NSString *result = [[NSString alloc] initWithFormat:format arguments:args];
   va_end (args);
   return result;
}

(与@A-Live 的基本相同)。

将适当检查其用途,例如:

int x;
...
NSString *s1 = GetLocalStr(@"name = %d", x); // OK
NSString *s2 = GetLocalStr(@"name = %d");    // compile warning - More '%" conversions than data arguments
NSString *s3 = GetLocalStr(@"name", x);      // compile warning - Data argument not used by format string
NSString *s4 = GetLocalStr(@"name");         // OK

【讨论】:

  • 您使用的是哪个编译器和 SDK? -localizedStringForKey:value:table: 应该已经用 NS_FORMAT_ARGUMENT 注释,所以不需要额外的 localize() 函数。
  • @GregParker - Dang,我重现了 OP 的错误,假设它是由于缺少属性,添加到函数中,然后修复了它。原来我犯了自己的错误,更新了答案。谢谢。
  • @CRD - 感谢您的详细回答 - 它帮助我更多地澄清了问题(我已经更新了问题)。调用GetLocalStr( @"name", @"Foo" ) 工作正常,但是GetLocalStr( @"name" ) 是发出警告的原因。我没有看到将格式说明符添加到密钥有任何区别 - 没有它似乎可以正常工作,但也许我误解了一些东西
  • 附带说明 - 您介意解释一下1NS_FORMAT_ARGUMENT(1); 属性中的含义吗?
  • @divillysausages - 格式检查取决于编译器“查看”格式的能力,其精确定义可能取决于编译器版本。通常,编译器无法确定NSString * 变量的值,因此您会收到警告,但在某些情况下可以 - 编译器有时甚至可以看到嵌入在条件表达式中的格式 (?:)。如果您希望在多个位置使用的头文件中定义常量,您可以使用#define <name> <value>static NSString * const <name> <value>;,定义应该是“可见的”并执行格式检查。
【解决方案2】:

此变体不会产生警告(因为总是有 va_list):

NSString* GetLocalStr1(NSString *formatKey, ...)  {
    va_list ap;
    va_start(ap, formatKey);
    NSString * format = [[NSBundle mainBundle] localizedStringForKey:formatKey value:@"" table:nil];
    NSString *result =  [[NSString alloc] initWithFormat:format arguments:ap];
    va_end (ap);
    return [result autorelease];
}

...

__unused NSString * str = GetLocalStr1( @"name", @"Foo" );
__unused NSString * str1 = GetLocalStr1( @"TestNoArgs" );
__unused NSString * str2 = GetLocalStr1( @"TestArgs", @"Foo" );

NSLog(@"%@", str);
NSLog(@"%@", str1);
NSLog(@"%@", str2);

结果:

我的名字是福

TestNoArgs

Hello World Foo

它不能准确回答问题,但可能会帮助您在找到解决方案之前避免警告。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-19
    • 2010-12-13
    相关资源
    最近更新 更多