这里没有软件测试的泛泛理论,只有博主的最佳实践。 博主的研究方向为静态分析和性能测试,致力于各种测试工具的引入、评估和开发。 本博的测试文章均为作者原创,转载请务必注明出处。

C/C++ 代码错误总结(2)

上一篇 / 下一篇  2008-01-25 10:58:58 / 个人分类:静态分析

接着总结。

5 RETURN_LOCAL

函数的返回值是局部变量(堆栈)的地址。

C/C++语言中,当被调用函数执行完毕,其所有的局部变量就不再有效,它们的内存区域可能会被新的调用中的变量所覆盖。所以,指向局部(堆栈)变量的指针不应该被返回,否则将会引起内存冲突和未明确的行为。

例子请参考blog上的另外一篇文章《一个中软的关于内存的笔试题》的第二题。

6      UNINIT

变量在使用前没有初始化。

如果没有初始化的话,堆栈中的变量没有固定的数值。使用了没有初始化的变量会导致不可预测的行为,或者安全漏洞。

举例说明:

int uninit_example1(int c) {

int x;

if(c)

return c;

else

return x; // "x" is not initialized

}

 

int result;

int uninit_example2(int c) {

int *x;

if(c)

x = &c;

use (x); // uninitialized variable "x" and "*x" used in call

}

void use (int *x) {

result = *x+2;

}

 

7      MISSING_RETURN

在返回类型为非void的函数中,值没有被返回。比较常见的情况是,在不同的分支要返回不同的值,结果有一个分支漏了return

举例:

int fn(int x)

{

switch (x) {

case 5: return 4;

default: return 5;

}

// no return; but not a defect, since unreachable

}

8        INFINITE_LOOP

程序中有不能终止的循环,会导致程序挂起或者崩溃。当然在多任务的程序中,故意设计的死循环除外。

9      DEADCODE

由于条件判断总是TRUEFALSE,导致一个分支内的代码永远执行不到,这些永远都执行不到的代码称为“死代码”。

表面看,死代码好像不是什么大问题,无非就是让代码规模大了一点;但反过来想,正常情况下,没有哪个程序员去故意写“死代码”,即便是防御性的异常处理代码,都应该是有可能执行到的。所以说,程序中存在“死代码”一般是由逻辑错误引起的。在情况严重时,这些逻辑错误可能导致重要的代码永远不能执行,造成严重的后果。

举例说明

int deadcode_example2(int array[10]) {

int i;

int value = -1;

for( i = 0; i < 10; i++ ) { // ERROR: increment is unreachable; array

// not properly searched

if( array[i] > 100 ) // the break below probably meant to be

value = array[i];    // part of the true branch of the if statement

break;              // but there are no braces here

}

return value;

}

 

10 OVERRUN_STATIC

主要是指数组或者指针的索引为常量情况下的越界。

最常见的安全漏洞是“缓冲区溢出(buffer overrun)”。缓冲区溢出可以导致内存访问超出边界,导致内存冲突,造成很难定位的安全漏洞,黑客有可能通过该漏洞控制系统。

举例说明:

void overrun_pointer() {

int buf[10]; // buff has 10 elements, index 0 to 9

int *x = &buff[1]; // x now points to buff[1]

x[9] = 0; // x[9] is equivalent to buff[10],

// which is out of bounds

}

11 OVERRUN_DYNAMIC

和静态的溢出对应,这里主要说的是数组或索引在动态访问内存的时候越界。

由于C/C++语言固有的不安全性,数组或指针的索引不会自动去做边界检查,缓冲区溢出的情况非常普遍,所以C/C++的程序员有必要自己检查索引是否在有效区间内,但在复杂的函数调用和数据流传递中,这项工作变得异常复杂。

举例说明。

void bad_heap(){

int *buffer = (int *) malloc(10 * sizeof(int)); // allocates 40 bytes

// (10 * 4 byte ints)

int i = 0; // valid indices are buffer[0] to buffer[9]

for(; i <= 10; i++) { // overruns memory by writing buffer[10]

buffer[i] = i;

}

}

12 NEGATIVE_RETURNS

函数返回的值可能是负整数。在使用之前(例如数组索引、循环标志变量、代数表达式、函数调用的关于长度大小的参数),应该检查函数返回值的正确性。

负整数的错误使用会导致严重的问题,比如死循环、数组越界(内存冲突)变量溢出等。最常见的错误是将一个无符号的值赋给有符号的整型变量,然后把该变量作为数组的索引。另外一种情况是将函数返回的负值直接或者隐式的转换成无符号整型变量,这将会导致一个非常大的值,如果该变量正好作为数组的索引或者内存申请的参数,一个进程将会申请大量的内存或者非常大的一个循环等。

举例说明。

void basic_negative() {

int buff[1024];

int x = some_function(); // some_function() might return -1

buff[x] = 0; // ERROR: buffer underrun at buff[-1]

}

 

void subtle_negative() {

unsigned x;

x = signed_count_func(); // returns signed -1 on error

// -1 cast to an unsigned is a very large integer

loop_with_param(x); // uses x as an upper bound

// ERROR, loop might never end or last too long

}

 

void another_subtle_negative(){

unsigned int c;

for (i = 0; (c=read(fd, buf, sizeof(buf)))>0; i+=c) // read returns -1 on

// error

// c is now a very large integer

if (write(1, buf, c) != c) // ERROR: attempts to write too many bytes to stdout

die("Write call failed");

}

 

今天就到这里,明天继续。


TAG: 静态分析

 

评分:0

我来说两句

Open Toolbar