下面是我们的一些代码的简化,似乎在演示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:
最佳答案
一个未知数是编译器选择用来表示myEnum
的基础整数类型。从需要对单独编译的文件可链接在一起的选择进行确定性的意义上来说,这是“实现定义的”,但在编译器的文档说明了如何选择此类型的意义上,这不是实现定义的。选择取决于枚举的定义,任何描述都只能是一种算法。
无论阴影如何,我都认为该函数已定义(对于任何参数,它都不会从未初始化的myValue
中读取)。换句话说,警告是误报。我已经用另一个可以检测未初始化内存使用的静态分析器来“验证”了这一点。
要消除“ myEnum
的整数类型”的阴影,您可以做的就是发布clang-the-compiler生成的汇编代码。如果汇编代码中存在未初始化的访问,将更容易理解原因。
这里可能会发生什么,但是像Clang这样的功能齐全的静态分析器是一门复杂的野兽,对不熟悉其内部结构的人的解释应该带有一点盐味,那就是为为myEnum
选择0x8000时,value1
是不同的,而不是较小的值。对于较小的值,myEnum
的基础类型可以是带符号的16位short int
,而0x8000强制编译器使用unsigned short int
。 myEnum
的这种不同类型将在表示该函数的抽象语法树中引入更多隐式转换,使其更难预测,并导致误报。我不在Clang上工作,但可以向您保证,这些隐式转换始终很难在C的静态分析器中处理。
lang开发人员考虑误报错误,他们当然希望听到这一错误。 homepage说:
请通过举报误报来帮助我们实现这一目标
这句话直接链接到有关如何提交错误的解释。
关于c - lang分析器误报还是溢出?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/16311092/