【问题标题】:Clang Static Analyzer False Positive with bit-fields (C-code)带有位域的 Clang 静态分析器误报(C 代码)
【发布时间】:2019-05-13 01:30:26
【问题描述】:

我想知道以下示例是否是 Clang SA 误报,如果是,有没有办法抑制它?

这里的关键是我正在复制一个包含位域的结构,方法是将其转换为一个单词而不是逐个域的副本(或 memcpy)。逐字段复制和 memcpy 都不会触发警告,但是作为单词复制(在转换之后)会引发“未初始化访问”警告。这是在嵌入式系统上,只有单词访问是可能的,并且这些类型的单词副本很常见。

下面是示例代码:

#include <stdio.h>
#include <string.h>

struct my_fields_t {
  unsigned int f0: 16;
  unsigned int f1: 8;
  unsigned int f2: 8;
};

int main(void) {

  struct my_fields_t var1, var2;

  // initialize all the fields in var1.
  var1.f0 = 1;
  var1.f1 = 2;
  var1.f2 = 3;

  // Method #1: copy var1 -> var2 as a word (sizeof(unsigned int) = 4).
  unsigned int *src = (unsigned int *) &var1;
  unsigned int *dest = (unsigned int *) &var2;
  *dest = *src;

  // Method #2: copy var1->var2 field-by-field [NO SA WARNINGS]
  // var2.f0 = var1.f0;
  // var2.f1 = var1.f1;
  // var2.f2 = var1.f2;

  // Method #3: use memcpy to copy var1 to var2 [NO SA WARNINGS]
  // memcpy(&var2, &var1, sizeof(struct my_fields_t));

  printf("%d, %d, %d\n", var1.f0, var1.f1, var1.f2);
  printf("%d, %d, %d\n", var2.f0, var2.f1, var2.f2);  // <--- Function call argument is an uninitialized value
  printf("sizeof(unsigned int) = %ld\n", sizeof(unsigned int));
}

这是输出:

$ clang --version
clang version 4.0.0 (tags/RELEASE_401/final)
Target: x86_64-unknown-linux-gnu
Thread model: posix

$ clang -Wall clang_sa.c

$ ./a.out
1, 2, 3
1, 2, 3
sizeof(unsigned int) = 4

$ scan-build clang clang_sa.c
scan-build: Using '<snipped>/clang-4.0' for static analysis
clang_sa.c:33:3: warning: Function call argument is an uninitialized value
  printf("%d, %d, %d\n", var2.f0, var2.f1, var2.f2);  // <--- Function call argument is an uninitialized value
  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 warning generated.
scan-build: 1 bug found.

在上面的例子中,很明显var2中的所有字段都会被copy这个词初始化。所以,clang SA 不应该抱怨未初始化的访问。

感谢任何帮助/见解。

【问题讨论】:

  • 您实际上是在询问是否允许 unsigned intunsigned int 位字段起别名。我不知道 - 它可能在标准中没有明确定义。声音修复#1 是避免像瘟疫这样的位域。声音修复 #2 是为了避免高度可疑的指针转换。
  • 位域在内存有限、插入和提取域的高效指令、编译器支持以及不要求可移植性的嵌入式系统中占有一席之地。我本可以在没有位域的情况下编写上面的示例(即,使用 uint16 和 uint8 表示 f0-2 而不是位域),它会得到相同的结果。指针转换与实际问题无关,该问题旨在生成一个简单的示例来说明误报。
  • 不,除了布尔 blob,它们在任何地方都没有位置,你从哪里得到这个想法?它们尤其不应该用于与硬件相关的编程中,因为它们可能会造成最大的伤害。与按位运算符相比,它们绝对没有任何优势。两者都将转换为相关的指令集。当然,除了位域很可能转换为完全错误的机器代码,因为您甚至不知道哪个位是 msb。在 gcc 等许多编译器上,位域也往往会导致内存开销。

标签: c clang llvm-clang clang-static-analyzer


【解决方案1】:

在抑制特定警告方面,来自documentation

问:如何抑制特定的分析器警告?
目前没有可靠的机制来抑制分析器警告,尽管目前正在调查中。 ...

但在下一个问题中,它向您展示了您可以在静态分析期间标记要跳过的代码块,方法是用 #ifdef 块包围代码:

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

所以,你可以这样做:

#ifdef __clang_analyzer__
    #define COPY_STRUCT(DEST, SRC) (DEST) = (SRC)
#else
    #define COPY_STRUCT(DEST, SRC) do { \
        const unsigned int *src = (const void *)&(SRC); \
        unsigned int *dest = (void *)&(DEST); \
        *dest = *src; \
    } while(0)
#endif

COPY_STRUCT(var2, var1);

【讨论】:

  • 正如我在帖子中提到的,这是针对只能进行字对齐写入的嵌入式系统(var2 = var1 不一定会为下面的字副本生成代码)。原始代码使用易失性字指针来确保写入仅以字分辨率发生——我在所示示例中对此进行了简化。无论如何,这不应该被 Clang SA 标记,不是吗?如果这不是已知限制,我会考虑提交错误报告。
  • 转换为 volatile unsigned int * 将保证字对齐写入。我删除了示例中的 volatile 关键字以简化事情并生成一个简约的示例。感谢__clang_analyzer__ 的建议。除了将其作为错误报告提交给 Clang SA 项目之外,我还将尝试此操作。
  • 所使用的结构也应该是字对齐的(使用对齐指令)。同样,这些细节对于问题的症结来说是不必要的(即 Clang SA 抛出了误报) - 因此,它们不在示例中。
  • 谢谢,现在我将使用 __clang_analyzer__ 作为解决方法,并为 clang SA 提交 LLVM 错误报告。
猜你喜欢
  • 2010-11-11
  • 2013-10-28
  • 2011-03-28
  • 1970-01-01
  • 1970-01-01
  • 2010-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多