2013-05-01 78 views
0

下面是我们的一些代码的简化,它似乎是在clang分析器中演示了一个bug,尽管可能在我们的代码中存在一个真正的bug。铛分析仪假阳性或溢出?

typedef enum { 
    value1 = 0x8000, /*If value1 is initialized at < 0x8000, 
         the bug doesn't occur*/ 
    value2, 
    value3, 
    value4, 
    value5, 
    value6 
}myEnum; 

static bool test_UTIL(bool aBool, UINT16 iCaseValue) 
{ 
    bool canMatch = true; 
    int myValue; /*not initialized*/ 

    if (aBool) 
     myValue = 1; /*initialized */ 
    else 
     canMatch = ((value1 == iCaseValue) 
      || (value2 == iCaseValue) 
      || (value3 == iCaseValue) 
      || (value4 == iCaseValue) 
      || (value5 == iCaseValue) 
      || (value6 == iCaseValue)); 

    if (canMatch) 
    { 
     switch (iCaseValue) 
     { 
      case value1: 
      case value2: 
      case value3: 
      case value4: 
      case value5: 
      case value6: 
       break; 

      default: 
       /*This triggers a clang warning, claiming myValue is undefined*/ 
      canMatch = (iCaseValue == myValue); 
      break; 
     } 
    } 

    return canMatch; 
} 

正如评论指出的那样,错误时枚举在为0x8000的范围内,这将是符号位,如果不是无符号开始只发生。是否有可能以某种方式在switch语句中隐式转换为带符号的16位整数?或者是Clang困惑?

当然,这个例子可能会被重构来实现等价的行为,但是基于20年前的代码是不值得重写的,只是为了满足错误的分析器警告。

编辑:我已经添加了由下面的test_UTIL()函数生成的程序集。我无法读取组件足以在这里发现了一个问题,但其他人可能也感兴趣:

_test_UTIL:        ## @test_UTIL 
Ltmp15: 
    .cfi_startproc 
Lfunc_begin1: 
    .loc 1 24 0     ## /Users/jbrooks/Desktop/test/test/main.c:24:0 
## BB#0: 
    pushq %rbp 
Ltmp16: 
    .cfi_def_cfa_offset 16 
Ltmp17: 
    .cfi_offset %rbp, -16 
    movq %rsp, %rbp 
Ltmp18: 
    .cfi_def_cfa_register %rbp 
    movw %si, %ax 
    movl %edi, -4(%rbp) 
    movw %ax, -6(%rbp) 
    .loc 1 25 22 prologue_end ## /Users/jbrooks/Desktop/test/test/main.c:25:22 
Ltmp19: 
    movl $1, -12(%rbp) 
    .loc 1 28 2     ## /Users/jbrooks/Desktop/test/test/main.c:28:2 
    cmpl $0, -4(%rbp) 
    je LBB1_2 
## BB#1: 
    .loc 1 29 3     ## /Users/jbrooks/Desktop/test/test/main.c:29:3 
    movl $1, -16(%rbp) 
    jmp LBB1_9 
LBB1_2: 
    movb $1, %al 
    movl $32768, %ecx   ## imm = 0x8000 
    .loc 1 31 3     ## /Users/jbrooks/Desktop/test/test/main.c:31:3 
    movzwl -6(%rbp), %edx 
    cmpl %edx, %ecx 
    movb %al, -17(%rbp)   ## 1-byte Spill 
    je LBB1_8 
## BB#3: 
    movb $1, %al 
    movl $32769, %ecx   ## imm = 0x8001 
    movzwl -6(%rbp), %edx 
    cmpl %edx, %ecx 
    movb %al, -17(%rbp)   ## 1-byte Spill 
    je LBB1_8 
## BB#4: 
    movb $1, %al 
    movl $32770, %ecx   ## imm = 0x8002 
    movzwl -6(%rbp), %edx 
    cmpl %edx, %ecx 
    movb %al, -17(%rbp)   ## 1-byte Spill 
    je LBB1_8 
## BB#5: 
    movb $1, %al 
    movl $32771, %ecx   ## imm = 0x8003 
    movzwl -6(%rbp), %edx 
    cmpl %edx, %ecx 
    movb %al, -17(%rbp)   ## 1-byte Spill 
    je LBB1_8 
## BB#6: 
    movb $1, %al 
    movl $32772, %ecx   ## imm = 0x8004 
    movzwl -6(%rbp), %edx 
    cmpl %edx, %ecx 
    movb %al, -17(%rbp)   ## 1-byte Spill 
    je LBB1_8 
## BB#7: 
    movl $32773, %eax   ## imm = 0x8005 
    movzwl -6(%rbp), %ecx 
    cmpl %ecx, %eax 
    sete %dl 
    movb %dl, -17(%rbp)   ## 1-byte Spill 
LBB1_8: 
    movb -17(%rbp), %al   ## 1-byte Reload 
    andb $1, %al 
    movzbl %al, %ecx 
    movl %ecx, -12(%rbp) 
LBB1_9: 
    .loc 1 38 2     ## /Users/jbrooks/Desktop/test/test/main.c:38:2 
    cmpl $0, -12(%rbp) 
    je LBB1_14 
## BB#10: 
    .loc 1 40 3     ## /Users/jbrooks/Desktop/test/test/main.c:40:3 
Ltmp20: 
    movzwl -6(%rbp), %eax 
    leal -32768(%rax), %eax 
    cmpl $5, %eax 
    ja LBB1_12 
    jmp LBB1_11 
LBB1_11: 
    .loc 1 48 5     ## /Users/jbrooks/Desktop/test/test/main.c:48:5 
Ltmp21: 
    jmp LBB1_13 
LBB1_12: 
    .loc 1 52 5     ## /Users/jbrooks/Desktop/test/test/main.c:52:5 
    movzwl -6(%rbp), %eax 
    cmpl -16(%rbp), %eax 
    sete %cl 
    andb $1, %cl 
    movzbl %cl, %eax 
    movl %eax, -12(%rbp) 
Ltmp22: 
LBB1_13: 
LBB1_14: 
    .loc 1 57 2     ## /Users/jbrooks/Desktop/test/test/main.c:57:2 
    movl -12(%rbp), %eax 
    popq %rbp 
    ret 
Ltmp23: 
Lfunc_end1: 
+1

这是在一个普通的32位或64位系统上,还是在嵌入式代码中,sizeof(int)== 2(它仍然是合法的)? 'enum'的基本类型是'int',所以如果你有16位'int','0x8000'会被签名并且会导致问题。不过,我不确定我是否认为这是可能的解释。 – 2013-05-01 01:50:52

+0

这是一个很好的建议。但是,这是在常规的32位系统上。 – 2013-05-01 03:01:51

回答

1

一个未知因素是由编译器选择代表myEnum底层整数类型。这是在这个意义上“实现定义”的选择必须是确定性的单独编译的文件是可连接在一起的,但它不是实现定义在这个意义上,编译器的文档解释这种类型是如何选择的。选择取决于枚举的定义,任何描述只能是一个算法。

无论这个阴影如何,我认为函数是定义的(它不会从未初始化的myValue中读取任何参数)。换句话说,警告是误报。我用另一个检测未初始化内存使用情况的静态分析器“验证”了这一点。

你可以做些什么来解除“myEnum的整数类型”阴影是post-clang-the-compiler生成的汇编代码。如果汇编代码中存在未初始化的访问权限,则会更容易理解原因。


什么可能在这里发生的事情,而是一个全功能的静态分析,如锵是一个复杂的野兽,从别人来说明谁是不熟悉其内部应该半信半疑地看待,当针对value1挑选0x8000而不是较小的值时,为myEnum选择的基础整数类型不同。对于较小的值,myEnum的基础类型可能是有符号的16位short int,而0x8000会强制编译器使用unsigned short intmyEnum的这种不同类型会在抽象语法树中引入更多隐式转换,从而导致更难以预测并导致误报。我不上锵工作,但我可以向你保证,这些隐式转换总是在静态分析来处理C.

疼痛

锵开发商考虑误报的错误,他们当然希望听到这个一。该homepage说:

请直接误报

和这句话的链接就如何提交bug的解释有助于我们的这一努力。

+0

感谢您的详尽解答。一旦我对我们的代码没有错误有信心,我打算向Clang报告。我还将该程序集添加到原始问题中。你能否很好地阅读大会以发现问题? – 2013-05-01 17:51:05

+0

@JonBrooks'canMatch'是'-12(%rbp)'。 'myValue'是'-16(%rbp)'。当后者通过'cmpl -16(%ebp),%eax'访问时,它看起来不像它可能仍然是未初始化的。程序集主要用于确认“value1”,...,“value6”的值以及它们与“iCaseValue”的比较方式。 'switch'是超优化的:它仅仅是'-alix -32768(%rax),%eax; cmpl $ 5,%eax'。交换机和长分隔符都被正确编译,尽管长分隔符不是**优化的。你已经尽力了,你现在可以报告误报。 – 2013-05-01 18:08:33

+0

感谢您的帮助! – 2013-05-01 23:55:51