抓到了Splint的一只奇怪的bug

2014-05-08

C, Splint, Bug

先看一段还算正常的代码:

1 int (*f) ();
2 void g() {
3     int n = f();
4 }
5 int main() {return 0;}

这段代码虽然没干什么事情,但也没什么严重的问题,用gcc和clang都能无警告编译通过。

它定义了一个函数指针(*f) ()。然后我们在函数g()中调用了f(),把返回值放进了int n

用Splint过一遍,一个警告,因为n没有被使用。

然后我们要开始做一些邪恶的奇怪的事情了:

1 int (*(f)) ();
2 void g() {
3     int n = (f());;
4 }
5 int main() {return 0;}

加了两对括号和一个额外的分号(空语句)。我们再编译一下,编译器仍然一声不吭。

比较它们的编译结果,发现完全相同,截取g()函数的编译结果:

1 pushq   %rbp
2 movq    %rsp, %rbp
3 subq    $16, %rsp
4 movq    f(%rip), %rdx
5 movl    $0, %eax
6 call    *%rdx
7 movl    %eax, -4(%rbp)
8 leave
9 ret

但是Splint不干了:

 1 Splint 3.1.2 --- 03 May 2009
 2 
 3 constraintResolve.c:1517: at source point
 4 test.c:4:2:
 5     *** Internal Bug at constraintResolve.c:1517: llassert failed:
 6     constraint_isDefined(c) [errno: 25]
 7      *** Please report bug to submit@bugs.debian.org (via reportbug) ***
 8        (attempting to continue, results may be incorrect)
 9 constraint.c:1195: at source point
10 test.c:4:2:
11     *** Internal Bug at constraint.c:1195: llassert failed:
12     constraint_isDefined (c) [errno: 25]
13      *** Please report bug to submit@bugs.debian.org (via reportbug) ***
14        (attempting to continue, results may be incorrect)
15 *** Segmentation Violation
16 *** Location (not trusted): test.c:4:2
17 *** Last code point: transferChecks.c:4415
18 *** Previous code point: transferChecks.c:4002
19 *** Please report bug to submit@bugs.debian.org (via reportbug)
20 *** A useful bug report should include everything we need to reproduce the bug.

对代码再作一些调整,发现两对括号、一个分号必须都加上才会触发这个内部错误。

再尝试不同的修改,观察到Splint似乎把f看作了一个二阶的指针,于是解一层引用,内部错误消失了:

1 int (*(f)) ();
2 void g() {
3     int n = ((*f)());;
4 }
5 int main() {return 0;}

是这样吗?

1 int (*(f)) ();
2 void g() {
3     int n = (*f());;
4 }
5 int main() {return 0;}

错误还在。

一时半会推敲不出,作罢。

考虑到C的语法、语义中有许多复杂的细节,各个编译器(以及linter)会采用不同的实现方式(甚至会出错,就像上面的Splint那样),以后得更加小心。