2013-05-01 64 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