常问问题、如何处理常见的误报

  1. 我如何告诉分析器我不想在这里报告错误,因为我的自定义错误处理程序在出错之前会安全地结束执行?
  2. 分析器报告一个空解引用,但我知道指针永远不为空。如何告诉分析器指针永远不能为空?
  3. 如何告诉静态分析器我不关心特定的死存储?
  4. How do I tell the static analyzer that I don't care about a specific unused instance variable in Objective C?
  5. How do I tell the static analyzer that I don't care about a specific unlocalized string?
  6. How do I tell the analyzer that my instance variable does not need to be released in -dealloc under Manual Retain/Release?
  7. How do I decide whether a method's return type should be _Nullable or _Nonnull?
  8. How do I tell the analyzer that I am intentionally violating nullability?
  9. 分析器假定循环体从不被进入。我怎么能告诉它,循环体至少会被进入一次?
  10. 如何抑制特定分析器警告?
  11. 如何选择性排除分析仪检查的代码?

Q: 我如何告诉分析器我不想在这里报告错误,因为我的自定义错误处理程序在出错之前会安全地结束执行?

example custom assert

你可以通过自定义断言处理告诉分析器这个路径是不可达的。 例如,您可以修改代码段如下。

#ifndef CLANG_ANALYZER_NORETURN
#ifdef __clang__
#define CLANG_ANALYZER_NORETURN __attribute__((analyzer_noreturn))
#else
#define CLANG_ANALYZER_NORETURN
#endif
#endif
void customAssert() CLANG_ANALYZER_NORETURN;
int foo(int *b) {
  if (!b)
    customAssert();
  return *b;
}

Q: 分析器报告一个空解引用,但我知道指针永远不为空。如何告诉分析器指针永远不能为空?

example null pointer

分析器通常认为指针可能为空的原因是因为前面的代码检查将它与 null 进行了比较。所以如果您确定它绝对不可能为 null,删除前面的检查,并且,最好,添加一个断言。例如,在上面的代码段中,删除 if (!b) 检查就 O 了。

void usePointer(int *b);
int foo(int *b) {
  usePointer(b);
  return *b;
}

Q: 如何告诉静态分析器我不关心特定的死存储?

当分析器发现存储在变量中的值从未被使用时,它将产生类似于此的消息:

Value stored to 'x' is never read
编辑器同样也会产生类似警告:
warning: unused variable 'x' [-Wunused-variable]
如果编译器 CFLAGSCXXFLAGS 打开了 -Werror 会直接报错! 你可以使用(void)x; 虽然您的代码中有一个死存储,但您不希望报成 bug。

Q: How do I tell the static analyzer that I don't care about a specific unused instance variable in Objective C?

When the analyzer sees that a value stored into a variable is never used, it is going to produce a message similar to this one:

Instance variable 'commonName' in class 'HappyBird' is never used by the methods in its @implementation
You can add __attribute__((unused)) to the instance variable declaration to suppress the warning.

Q: How do I tell the static analyzer that I don't care about a specific unlocalized string?

When the analyzer sees that an unlocalized string is passed to a method that will present that string to the user, it is going to produce a message similar to this one:

User-facing text should use localized string macro
If your project deliberately uses unlocalized user-facing strings (for example, in a debugging UI that is never shown to users), you can suppress the analyzer warnings (and document your intent) with a function that just returns its input but is annotated to return a localized string:
__attribute__((annotate("returns_localized_nsstring")))
static inline NSString *LocalizationNotNeeded(NSString *s) {
  return s;
}
You can then call this function when creating your debugging UI:
[field setStringValue:LocalizationNotNeeded(@"Debug")];
Some projects may also find it useful to use NSLocalizedString but add "DNL" or "Do Not Localize" to the string contents as a convention:
UILabel *testLabel = [[UILabel alloc] init];
NSString *s = NSLocalizedString(@"Hello <Do Not Localize>", @"For debug purposes");
[testLabel setText:s];

Q: How do I tell the analyzer that my instance variable does not need to be released in -dealloc under Manual Retain/Release?

If your class only uses an instance variable for part of its lifetime, it may maintain an invariant guaranteeing that the instance variable is always released before -dealloc. In this case, you can silence a warning about a missing release by either adding assert(_ivar == nil) or an explicit release [_ivar release] (which will be a no-op when the variable is nil) in -dealloc.

Q: How do I decide whether a method's return type should be _Nullable or _Nonnull?

Depending on the implementation of the method, this puts you in one of five situations:

  1. You actually never return nil.
  2. You do return nil sometimes, and callers are supposed to handle that. This includes cases where your method is documented to return nil given certain inputs.
  3. You return nil based on some external condition (such as an out-of-memory error), but the client can't do anything about it either.
  4. You return nil only when the caller passes input documented to be invalid. That means it's the client's fault.
  5. You return nil in some totally undocumented case.

In (1) you should annotate the method as returning a _Nonnull object.

In (2) the method should be marked _Nullable.

In (3) you should probably annotate the method _Nonnull. Why? Because no callers will actually check for nil, given that they can't do anything about the situation and don't know what went wrong. At this point things have gone so poorly that there's basically no way to recover.

The least happy case is (4) because the resulting program will almost certainly either crash or just silently do the wrong thing. If this is a new method or you control the callers, you can use NSParameterAssert() (or the equivalent) to check the precondition and remove the nil return. But if you don't control the callers and they rely on this behavior, you should return mark the method _Nonnull and return nil cast to _Nonnull anyway. (Note that (4) doesn't apply in cases where the caller can't know they passed bad parameters. For example, +[NSData dataWithContentsOfFile:options:error:] will fail if the file doesn't exist, but there's no way to check for that in advance. This means you're really in (2).)

If you're in (5), document it, then figure out if you're now in (2), (3), or (4). :-)

Q: How do I tell the analyzer that I am intentionally violating nullability?

In some cases, it may make sense for methods to intentionally violate nullability. For example, your method may — for reasons of backward compatibility — chose to return nil and log an error message in a method with a non-null return type when the client violated a documented precondition rather than check the precondition with NSAssert(). In these cases, you can suppress the analyzer warning with a cast:

    return (id _Nonnull)nil;
Note that this cast does not affect code generation.

Q: 分析器假定循环体从不被进入。我怎么能告诉它,循环体至少会被进入一次?

example use assert

在上面的示例中,分析器已经检测到对于 length <= 0 的情况,从未进入循环体。在该特定示例中,您确定会进入循环,因为在调用该函数时,输入参数 length 大于零。您可以用断言来教分析器。在函数开头添加 assert(length > 0),您可以告诉分析器您的代码绝不会有零或负值,因此不需要测试这些路径的正确性。

int foo(int length) {
  int x = 0;
  assert(length > 0);
  for (int i = 0; i < length; i++)
    x += 1;
  return length/x;
}

Q: 如何抑制特定分析器警告?

目前没有用于抑制分析器警告的固定机制,虽然目前还正在研究。当遇到分析器错误/误报时,检查它是否是上面讨论的问题,或者分析器注释是否可以解决问题。其次,请报告它,以帮助我们改善用户体验。作为最后的手段,请考虑使用下面所述__clang_analyzer__ 宏。

Q: 如何选择性排除分析仪检查的代码?

当静态分析器使用 clang 来解析源文件时,它会隐式地定义预处理器宏 __clang_analyzer__。可以使用此宏选择性地排除分析器检查的代码。例子:

#ifndef __clang_analyzer__
// 不被分析的代码
#endif
不鼓励这种用法,因为它使代码停止分析器。相反,我们更喜欢用户在分析器误报时报告 bug。