0

下面是我们的一些代码的简化,它似乎展示了 clang 分析器中的一个错误,尽管我们的代码中可能存在一个真正的错误。

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:
4

1 回答 1

1

一个未知数是编译器选择来表示的底层整数类型myEnum。这是“实现定义的”,因为选择需要确定性以便单独编译的文件可以链接在一起,但它不是实现定义的,因为编译器的文档解释了如何选择这种类型。选择取决于枚举的定义,任何描述都只能是算法。

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

要消除“整数类型”的阴影,您可以做的myEnum是发布编译器生成的汇编代码。如果汇编代码中存在未初始化的访问,则更容易理解原因。


这里可能会发生什么,但是像 Clang 这样的全功能静态分析器是一个复杂的野兽,来自不熟悉其内部结构的人的解释应该持保留态度,即选择的基础整数类型myEnumis different when 0x8000 is picked for value1as opposed to smaller values. 对于较小的值,底层类型myEnum可能是带符号的 16 位short int,而 0x8000 强制编译器使用unsigned short int. 这种不同类型的 formyEnum将在表示函数的抽象语法树中引入更多隐式转换,使其更难预测,并导致误报。我不在 Clang 上工作,但我可以向你保证,在 C 的静态分析器中处理这些隐式转换总是很痛苦。


Clang 开发人员考虑误报错误,他们当然想听听这个错误。主页说:

请通过报告误报来帮助我们完成这项工作

这句话直接链接到如何提交错误的解释。

于 2013-05-01T08:32:40.643 回答