3.1. 调试检查

分析器包含一些检查器,可以帮助调试。使用 “-analyzer-checker=” 标志,后跟检查器的名称来启用它们。

3.1.1. 通用分析转储器

这些检查器用于将各种基础设施分析的结果转储到 stderr。一些检查器也有 “view” 变体,它们将使用 “dot” 格式查看器(如 macOS 上的 Graphviz)显示图表。

  • debug.DumpCallGraph, debug.ViewCallGraph:显示为当前翻译单元生成的调用图。这用于确定在启用内联时分析函数的顺序。

  • debug.DumpCFG, debug.ViewCFG:显示为每个正在分析的顶级函数生成的 CFG。

  • debug.DumpDominators:显示每个顶级函数的 CFG 的支配树。

  • debug.DumpLiveVars:显示为每个正在分析的顶级函数生成的活动变量分析结果。

  • debug.DumpLiveExprs:显示为每个正在分析的顶级函数生成的活动表达式分析结果。

  • debug.ViewExplodedGraph:显示为输入翻译单元中不同函数的分析生成的爆炸图。当分析多个函数时,每个函数显示一个图。注意,即使对于小函数,这些图也可能变得非常大。

3.1.2. 路径跟踪

这些检查器打印有关分析器引擎所采用的路径的信息。

  • debug.DumpCalls:打印出在路径遍历期间遇到的每个函数或方法调用。这是缩进以显示调用栈,但没有对分支进行任何特殊处理,这意味着不同的路径可能最终交织在一起。

  • debug.DumpTraversal:打印在路径遍历期间遇到的每个分支语句的名称(“IfStmt”、“WhileStmt” 等)。目前用于检查分析引擎是在执行 BFS 还是 DFS。

3.1.3. 状态检查

这些检查器将以分析警告的形式打印有关分析器状态的信息。它们旨在与回归测试中的 -verify 功能一起使用。

  • debug.TaintTest:为每个携带污点的表达式打印出 “tainted” 一词。在撰写本文时,污点仅由实验性检查器下的检查器引入。security.taint.TaintPropagation;此检查器最终可能会移至 security.taint 包。

  • debug.ExprInspection:响应某些函数调用,这些函数调用模拟内置函数。这些函数调用应该影响程序状态,而不是其参数的评估;要使用它们,您需要在测试文件中声明它们。可用函数如下所述。

(FIXME:debug.ExprInspection 可能应该重命名,因为它不再只检查表达式。)

3.1.3.1. ExprInspection 检查

  • void clang_analyzer_eval(bool);

    如果参数已知具有非零值,则打印 TRUE;如果参数已知具有零或空值,则打印 FALSE;如果参数在此路径上没有得到足够的约束,则打印 UNKNOWN。您可以使用诸如 “x == 5” 之类的表达式来测试其他值。请注意,此功能目前在内联函数中被禁用,因为对同一内联函数的不同调用可能会提供不同的信息,从而难以编写正确的 -verify 指令。

    在 C 中,参数可以类型化为 ‘int’ 或 ‘_Bool’。

    示例用法

    clang_analyzer_eval(x); // expected-warning{{UNKNOWN}}
    if (!x) return;
    clang_analyzer_eval(x); // expected-warning{{TRUE}}
    
  • void clang_analyzer_checkInlined(bool);

    如果调用发生在内联函数内,则根据其参数的值打印 TRUE 或 FALSE。如果调用发生在内联函数之外,则不会打印任何内容。

    此检查器的预期用途是断言函数至少内联一次(通过传递 ‘true’ 并期望收到警告),或者断言函数永远不会内联(通过传递 ‘false’ 并期望没有警告)。参数在技术上是不必要的,但旨在澄清意图。

    您可能想知道为什么我们不能在函数内联时打印 TRUE,而在函数不内联时打印 FALSE。问题是,任何内联函数都可能被视为顶级函数(在这种情况下,TRUE 和 FALSE 都会被打印),具体取决于 -analyzer-inlining 选项的值。

    在 C 中,参数可以类型化为 ‘int’ 或 ‘_Bool’。

    示例用法

    int inlined() {
      clang_analyzer_checkInlined(true); // expected-warning{{TRUE}}
      return 42;
    }
    
    void topLevel() {
      clang_analyzer_checkInlined(false); // no-warning (not inlined)
      int value = inlined();
      // This assertion will not be valid if the previous call was not inlined.
      clang_analyzer_eval(value == 42); // expected-warning{{TRUE}}
    }
    
  • void clang_analyzer_warnIfReached();

    如果分析器到达此行代码,则生成警告。

    示例用法

    if (true) {
      clang_analyzer_warnIfReached();  // expected-warning{{REACHABLE}}
    }
    else {
      clang_analyzer_warnIfReached();  // no-warning
    }
    
  • void clang_analyzer_numTimesReached();

    与上述相同,但包括分析器在当前分析期间到达此调用表达式的次数。

    示例用法

    for (int x = 0; x < 3; ++x) {
      clang_analyzer_numTimesReached(); // expected-warning{{3}}
    }
    
  • void clang_analyzer_warnOnDeadSymbol(int);

    当分析器垃圾回收代表参数值的符号时,订阅延迟警告。

    在调用 ‘clang_analyzer_warnOnDeadSymbol(x)’ 时,如果 ‘x’ 的值为符号,则 ExprInspection 检查器会标记此符号。然后,在每次垃圾回收运行期间,检查器会查看标记的符号是否正在被收集,如果被收集,则发出 ‘SYMBOL DEAD’ 警告。这样,您就知道符号究竟在哪里(直到代码行)死亡。

    您不太可能在符号已经死亡后调用此函数,因为对它的引用作为函数参数会阻止它死亡。但是,如果参数不是符号而是具体值,则不会发出警告。

    示例用法

    do {
      int x = generate_some_integer();
      clang_analyzer_warnOnDeadSymbol(x);
    } while(0);  // expected-warning{{SYMBOL DEAD}}
    
  • void clang_analyzer_explain(a single argument of any type);

    此函数以人类可读的方式在警告消息中解释其参数的值。您可以在测试代码中根据需要创建此函数原型的任意多个重写以解释各种整型、指针甚至记录类型的值。为了简化在 C 代码中的使用(在 C 代码中不允许重载函数声明),您可以在函数名称后添加任意后缀,而不会影响功能。

    示例用法

    void clang_analyzer_explain(int);
    void clang_analyzer_explain(void *);
    
    // Useful in C code
    void clang_analyzer_explain_int(int);
    
    void foo(int param, void *ptr) {
      clang_analyzer_explain(param); // expected-warning{{argument 'param'}}
      clang_analyzer_explain_int(param); // expected-warning{{argument 'param'}}
      if (!ptr)
        clang_analyzer_explain(ptr); // expected-warning{{memory address '0'}}
    }
    
  • void clang_analyzer_dump( /* a single argument of any type */);

    类似于 clang_analyzer_explain,但会生成值的原始转储,与 SVal::dump() 相同。

    示例用法

    void clang_analyzer_dump(int);
    void foo(int x) {
      clang_analyzer_dump(x); // expected-warning{{reg_$0<x>}}
    }
    
  • size_t clang_analyzer_getExtent(void *);

    此函数返回表示参数指向的内存区域范围的值。此值通常难以获得,因为没有产生此值的有效代码。但是,它可能对测试目的很有用,用于查看分析器对区域范围的建模效果如何。

    示例用法

    void foo() {
      int x, *y;
      size_t xs = clang_analyzer_getExtent(&x);
      clang_analyzer_explain(xs); // expected-warning{{'4'}}
      size_t ys = clang_analyzer_getExtent(&y);
      clang_analyzer_explain(ys); // expected-warning{{'8'}}
    }
    
  • void clang_analyzer_printState();

    将当前 ProgramState 转储到 stderr。快速查找任何执行点处的程序状态,而无需使用 ViewExplodedGraph 或重新编译程序。这对于编写测试(除了测试 ProgramState 的打印方式之外)不太有用,但对于调试测试很有用。此外,此方法不会产生警告,因此它会在所有其他 ExprInspection 警告之前打印在控制台上。

    示例用法

    void foo() {
      int x = 1;
      clang_analyzer_printState(); // Read the stderr!
    }
    
  • void clang_analyzer_hashDump(int);

    分析器可以生成一个哈希值来识别报告。要调试用于计算此哈希值的信息,可以使用上面的函数将哈希字符串作为任意表达式的警告转储。

    示例用法

    void foo() {
      int x = 1;
      clang_analyzer_hashDump(x); // expected-warning{{hashed string for x}}
    }
    
  • void clang_analyzer_denote(int, const char *);

    用字符串表示符号。后续对 clang_analyzer_express() 的调用将用这些字符串表示另一个符号。有助于测试不同符号之间的关系。

    示例用法

    void foo(int x) {
      clang_analyzer_denote(x, "$x");
      clang_analyzer_express(x + 1); // expected-warning{{$x + 1}}
    }
    
  • void clang_analyzer_express(int);

    参见 clang_analyzer_denote()。

  • void clang_analyzer_isTainted(a single argument of any type);

    查询分析器作为参数使用的表达式是否被污染。这在测试中很有用,我们不想对所有被污染的表达式发出警告,而只检查某些表达式。这将有助于减少 TaintTest 调试检查器会引入的噪音,并让您专注于您真正关心的 预期警告

    示例用法

    int read_integer() {
      int n;
      clang_analyzer_isTainted(n);     // expected-warning{{NO}}
      scanf("%d", &n);
      clang_analyzer_isTainted(n);     // expected-warning{{YES}}
      clang_analyzer_isTainted(n + 2); // expected-warning{{YES}}
      clang_analyzer_isTainted(n > 0); // expected-warning{{YES}}
      int next_tainted_value = n; // no-warning
      return n;
    }
    
  • clang_analyzer_dumpExtent(a single argument of any type)

  • clang_analyzer_dumpElementCount(a single argument of any type)

    转储出参数的范围和元素计数。

    示例用法

    void array() {
      int a[] = {1, 3};
      clang_analyzer_dumpExtent(a);       // expected-warning {{8 S64b}}
      clang_analyzer_dumpElementCount(a); // expected-warning {{2 S64b}}
    }
    
  • clang_analyzer_value(a single argument of integer or pointer type)

    打印给定参数的关联值。支持的参数类型是整数、枚举和指针。该值可以表示为范围集或具体整数。对于其余类型,函数会打印 n/a(即不可用)。

    注意:对于使用 Z3 约束管理器构建的 clang,此函数将不会打印任何内容。这可能会导致您的测试崩溃。要管理此问题,请使用以下测试约束技术之一

    示例用法

    void print(char c, unsigned u) {
      clang_analyzer_value(c); // expected-warning {{8s:{ [-128, 127] }}}
      if(u != 42)
         clang_analyzer_value(u); // expected-warning {{32u:{ [0, 41], [43, 4294967295] }}}
      else
         clang_analyzer_value(u); // expected-warning {{32u:42}}
    }
    

3.1.4. 统计信息

debug.Stats 检查器收集有关每个函数分析的各种信息,例如到达了多少个块以及分析器是否超时。

还有一个额外的 -analyzer-stats 标志,它会在分析器引擎中启用各种统计信息。请注意,Stats 检查器(它会为每个函数生成至少一个错误报告)实际上可能会更改 -analyzer-stats 报告的值。

3.1.5. 输出测试检查器

  • debug.ReportStmts 在每个语句处都会报告警告,这使其成为全面测试错误报告构建和输出发射的非常有用的工具。