Chinaunix首页 | 论坛 | 博客
  • 博客访问: 380966
  • 博文数量: 158
  • 博客积分: 1227
  • 博客等级: 少尉
  • 技术积分: 946
  • 用 户 组: 普通用户
  • 注册时间: 2011-02-20 16:19
文章分类
文章存档

2016年(1)

2015年(1)

2012年(107)

2011年(49)

分类:

2012-03-26 13:29:19

 100个开源C/C++项目中的bugs

   http://www.oschina.net/question/1579_45444?from=20120325

本文演示静态代码分析的能力. 提供了100个已在开源C/C++项目中发现的错误例子给读者研究。所有的错误已经被PVS-Studio静态代码分析工具发现。

我们不想让你阅读文档感到厌倦,并马上给你传达错误例子.

那些想了解什么是的读者,请跟随链接。想了解什么是PVS-Studio的读者,可下载试用版.请看该页:

当然,还有一件事,请查看我们的帖子: ""

错误实例將被归为几类。这种分法是很相对性的。 同一个错误常常同时属于打印错误和不正确的数组操作错误.

当然我们仅从每个项目中列出少数几个错误。如果我们描述所以已发现的错误,那將要编写一本指南书了。以下是被分析的项目:

  • Apache HTTP Server -
  • Audacity -
  • Chromium -
  • Clang -
  • CMake -
  • Crystal Space 3D SDK -
  • Emule -
  • FAR Manager -
  • FCE Ultra -
  • Fennec Media Project -
  • G3D Content Pak -
  • IPP Samples -
  • Lugaru -
  • Miranda IM -
  • MySQL -
  • Newton Game Dynamics -
  • Notepad++ -
  • Pixie -
  • PNG library -
  • QT -
  • ReactOS -
  • Shareaza -
  • SMTP Client with SSL/TLS -
  • StrongDC++ -
  • Swiss-Army Knife of Trace -
  • TortoiseSVN -
  • Ultimate TCP/IP -
  • VirtualDub -
  • WinDjView -
  • WinMerge -
  • Wolfenstein 3D -
  • Crypto++ -
  • Quake-III-Arena -
  • And some others.
 

数组和字符串处理的错误是C/C++程序中最大的一类。它以程序员可以高效处理低级内存问题为代价. 本文中,我们將展示一小部分被PVS-Studio分析器发现的此类错误。但我们相信任何C/C++程序员都了解他们有多巨大和阴险.

例 1. Wolfenstein 3D项目。对象仅被部分清除:

1void CG_RegisterItemVisuals( int itemNum ) {
2  ...
3  itemInfo_t *itemInfo;
4  ...
5  memset( itemInfo, 0, sizeof( &itemInfo ) );
6  ...
7}

The error was found through the diagnostic: It's odd that the argument of sizeof() operator is the '&itemInfo' expression. cgame cg_weapons.c 1467.

sizeof() 操作符计算的是指针大小而非‘itemInfo_t’结构体大小。必须写成"sizeof(*itemInfo)"。

例 2. 3D项目。矩阵仅被部分清除:

1ID_INLINE mat3_t::mat3_t( float src[ 3 ][ 3 ] ) {
2  memcpy( mat, src, sizeof( src ) );
3}

The error was found through the diagnostic: sizeof()操作返回的是指针的大小,非数组的大小,在'sizeof(src)'表达式中. Splines math_matrix.h 94.

通常开发者期望 'sizeof(src)' 操作返回"3*3*sizeof(float)"字节的数组大小。但根据语言规范,'src'仅是一个指针,而不是一个数组。因此,该矩阵只被部分的复 制。‘'memcpy'’函数將拷贝4或8字节(指针大小),这依赖于代码是32位还是64位的.

如果你希望整个矩阵都被拷贝,你可以给函数传递该数组的引用。以下是正确的代码:

1ID_INLINE mat3_t::mat3_t( float (&src)[3][3] )
2{
3  memcpy( mat, src, sizeof( src ) );
4}

例 3. 项目。数组仅被部分清除:

The error was found through the : memset 函数接收指针和它的大小作为参数. 这可能会引发错误。检测第三个参数。far treelist.hpp 66.

最有可能的是, 计算待清除的元素数量的乘法操作丢失了, 而该代码须如下所示:

"memset(Last, 0, LastCount * sizeof(*Last));".

例 4. . 不正确的字符串长度计算.

01static const PCHAR Nv11Board = "NV11 (GeForce2) Board";
02static const PCHAR Nv11Chip = "Chip Rev B2";
03static const PCHAR Nv11Vendor = "NVidia Corporation";
04 
05BOOLEAN
06IsVesaBiosOk(...)
07{
08  ...
09  if (!(strncmp(Vendor, Nv11Vendor, sizeof(Nv11Vendor))) &&
10      !(strncmp(Product, Nv11Board, sizeof(Nv11Board))) &&
11      !(strncmp(Revision, Nv11Chip, sizeof(Nv11Chip))) &&
12      (OemRevision == 0x311))
13  ...
14}

该错误经由诊断:strncmp 函数接收了指针和它的大小做为参数。这可能是个错误。查看第三个参数。vga vbe.c 57

'strncmp'的函数的调用仅仅比较了前几个字符,而不是整个字符串。这里的错误是:sizeof()操作,在这种情况下用来计算字符串长度绝对不适宜。sizeof()操作实际上只计算了指针的大小而不是string的字节数量。

关于该错误最讨厌和阴险的是,该代码大多数时候都如预期的工作。99%的情况下,比较前几个字符就足够了。但剩下的1%能带给你愉快和长时间的调试过程。

例 5. 项目. 数据越界(明确的下标).

01struct ConvoluteFilterData {
02 long m[9];
03 long bias;
04 void *dyna_func;
05 DWORD dyna_size;
06 DWORD dyna_old_protect;
07 BOOL fClip;
08};
09 
10static unsigned long __fastcall do_conv(
11  unsigned long *data,
12  const ConvoluteFilterData *cfd,
13  long sflags, long pit)
14{
15  long rt0=cfd->m[9], gt0=cfd->m[9], bt0=cfd->m[9];
16  ...
17}

The code was found through the diagnostic: 数组可能越界。下标‘9’已经指向数组边界外. VirtualDub f_convolute.cpp 73

这不是一个真正的错误,但是个好的诊断。解释: http://www.viva64.com/go.php?url=756.

例 6. CPU Identifying Tool 项目. 数组越界(宏中的下标)

01#define FINDBUFFLEN 64  // Max buffer find/replace size
02...
03int WINAPI Sticky (...)
04{
05  ...
06  static char findWhat[FINDBUFFLEN] = {'\0'};
07  ...
08  findWhat[FINDBUFFLEN] = '\0';
09  ...
10}

The error was found through the diagnostic:数组可能越界.下标‘64’已经指向数组边界外。stickies stickies.cpp 7947

该错误与上例类似。末端 null 已被写到数组外。正确代码是:"findWhat[FINDBUFFLEN - 1] = '\0';".

例 7. 项目. 数组越界(不正确的表达式).

01typedef struct bot_state_s
02{
03  ...
04  char teamleader[32]; //netname of the team leader
05  ...
06}  bot_state_t;
07 
08void BotTeamAI( bot_state_t *bs ) {
09  ...
10  bs->teamleader[sizeof( bs->teamleader )] = '\0';
11  ...
12}

The error was found through the diagnostic: 数组可能越界. 'sizeof (bs->teamleader)' 下标已指向数组边界外。game ai_team.c 548

这是又一个数组越界的例子, 当使用了明确声明的下标时候. 这些例子说明了,这些不起眼的错误比看上去更广泛.

末端的 null 被写到了 'teamleader' 数组的外部。下面是正确代码:

1bs->teamleader[
2 sizeof(bs->teamleader) / sizeof(bs->teamleader[0]) - 1
3 ] = '\0';

Example 8. 项目. 仅字符串的部分被拷贝.

01typedef struct _textrangew
02{
03  CHARRANGE chrg;
04  LPWSTR lpstrText;
05} TEXTRANGEW;
06 
07const wchar_t* Utils::extractURLFromRichEdit(...)
08{
09  ...
10  ::CopyMemory(tr.lpstrText, L"mailto:", 7);
11  ...
12}

The error was found through the diagnostic: 一个 'memcpy' 函数的调用將导致缓冲区上溢或下溢。tabsrmm utils.cpp 1080

如果使用Unicode字符集,一个字符占用2或4字节(依赖于编译器使用的数据模型)而不是一字节。不幸的是,程序员们很容易忘记,你常常会在程序中看到类似该例子的污点。

'CopyMemory' 函数將只拷贝L"mailto:"字符串的部分,因为他处理字节,而不是字符。你可以使用一个更恰当的字符串拷贝函数修复该代码,或者,至少是,将 sizeof(wchar_t) 与 数字7 相乘.

例 9. 项目. 循环中的数组越界.

01static const struct {
02  DWORD   winerr;
03  int     doserr;
04} doserrors[] =
05{
06  ...
07};
08 
09static void
10la_dosmaperr(unsigned long e)
11{
12  ...
13  for (i = 0; i < sizeof(doserrors); i++)
14  {
15    if (doserrors[i].winerr == e)
16    {
17      errno = doserrors[i].doserr;
18      return;
19    }
20  }
21  ...
22}

The error was found through the diagnostic: 数组可能越界.'i'下标值可到达 367. cmlibarchive archive_windows.c 1140, 1142

该错误处理本身就包含了错误。sizeof() 操作符以字节数返回数组大小而不是里面的元素的数量。因此,该程序將在循环中试图搜索多于本应搜索的元素.下面是正确的循环:

1for (i = 0; i < sizeof(doserrors) / sizeof(*doserrors); i++)

例 10. CPU Identifying Tool 项目. A string is printed into itself.

01char * OSDetection ()
02{
03  ...
04  sprintf(szOperatingSystem,
05          "%sversion %d.%d %s (Build %d)",
06          szOperatingSystem,
07          osvi.dwMajorVersion,
08          osvi.dwMinorVersion,
09          osvi.szCSDVersion,
10          osvi.dwBuildNumber & 0xFFFF);
11  ...
12  sprintf (szOperatingSystem, "%s%s(Build %d)",
13           szOperatingSystem, osvi.szCSDVersion,
14           osvi.dwBuildNumber & 0xFFFF);
15  ...
16}

This error was found through the diagnostic: It is dangerous to print the string 'szOperatingSystem' into itself. stickies camel.cpp 572, 603

一个格式化字符串打印到自身的企图,可能会导致不良后果。

代码的执行结果依赖于输入数据,结果未知。可能的,结果会是一个无意义字符串或一个 將发生。

该错误可以归为 "脆弱代码" 一类.在某些程序中,向代码提供特殊字符,你可以利用这些代码片段触发缓冲区溢出或者被入侵者利用.

例 11. 项目。某字符串未获得足够内存

1int FCEUI_SetCheat(...)
2{
3  ...
4  if((t=(char *)realloc(next->name,strlen(name+1))))
5  ...
6}

The error was found through the diagnostic: 'realloc' 函数通过 'strlen(expr)' 申请了数量奇怪的内存. 可能正确的变体是 'strlen(expr) + 1'. fceux cheat.cpp 609

该错误由一个错误打印导致。strlen() 函数的参数必须是 "name" 指针而不是 "name+1" 指针。因此,realloc 函数比实际需要少申请了两个字节:1个字节丢失了,因为1没有被加入到字符串长度中。另一个字节也丢失了,因为'strlen'函数计算长度时跳过了第一 个字符。

例 12. 项目。部分的数组清理。

1#define CONT_MAP_MAX 50
2int _iContMap[CONT_MAP_MAX];
3...
4DockingManager::DockingManager()
5{
6  ...
7  memset(_iContMap, -1, CONT_MAP_MAX);
8  ...
9}

The error was found through the diagnostic:memset 函数的调用將导致缓冲区上溢或下溢. notepadPlus DockingManager.cpp 60

这是又一个数组元素数量与数组大小混淆的例子。一个通过 sizeof(int) 的乘法操作丢失了。

我们可以继续给你指出更多我们在各种项目中发现的数组处理错误例子。但我们不得不停止了。

首先,一小段理论知识

未定义行为是某些编程语言的特性(尤其在C和C++中),在某些情形下产生的结果將依赖于编译器的实现或指定的优化选项。换句话说,规范并没有定义 某情况下该语言的行为,仅仅是说:“在 A 条件下,B 结果是未定义的”。在这种情况下错误在你的程序中被认为是允许的,甚至在一些特别的编译器中执行良好。这样的程序不能跨平台,并有可能在不同的电脑,不同 的操作系统甚至不同的编译器设置中导致失败。

 一个时序点可以是程序中的任意点,它保证在它之前所有运算的副作用已完成,而后继运算的副作用未开始.学习更多关于时序和跟时序点相关的未定义行为,查看该贴:.

例 1. 项目。不正确的使用智能指针。

1void AccessibleContainsAccessible(...)
2{
3  ...
4  auto_ptr child_array(new VARIANT[child_count]);
5  ...
6}

The error was found through the diagnostic: Incorrect use of auto_ptr. The memory allocated with 'new []' will be cleaned using 'delete'. interactive_ui_tests accessibility_win_browsertest.cc 171

该例子演示了使用智能指针的时候,有可能导致未定义的行为。它可能通过堆破坏,程序崩溃,未完全的对象析构函数或任何其它的错误传达. 该错误是:内存被new [] 操作分配,而被‘auto_ptr'类构析函数里的 delete 操作释放:

1~auto_ptr() {
2  delete _Myptr;
3}

为修复这类问题,你应为实例使用一个更恰当的类,boost::scoped_array.

例 2. 项目。经典的未定义行为。

01template void HadamardFwdFast(...)
02{
03  Ipp32s *pTemp;
04  ...
05  for(j=0;j<4;j++) {
06    a[0] = pTemp[0*4] + pTemp[1*4];
07    a[1] = pTemp[0*4] - pTemp[1*4];
08    a[2] = pTemp[2*4] + pTemp[3*4];
09    a[3] = pTemp[2*4] - pTemp[3*4];
10    pTemp = pTemp++;
11    ...
12  }
13  ...
14}

The error was found through the diagnostic: 未定义行为. 'pTemp' 变量被修改且在时序点间使用了两次. me umc_me_cost_func.h 168

这是一个关于未定义程序行为的经典例子. 各类文章中都拿它来演示未定义行为. ‘pTemp'自增与否是未知的。两个改变 pTemp 变量值的操作位于一个时序点中.这意味着编译器可能创建如下的代码:

pTemp = pTemp + 1;

pTemp = pTemp;

也可能创建另一版本的代码:

TMP = pTemp;

pTemp = pTemp + 1;

pTemp = TMP;

创建何种版本的代码依赖于编译器和优化选项。

例 3. 项目。复杂的表达式。

1uint32 CUnBitArrayOld::DecodeValueRiceUnsigned(uint32 k)
2{
3  ...
4  while (!(m_pBitArray[m_nCurrentBitIndex >> 5] &
5    Powers_of_Two_Reversed[m_nCurrentBitIndex++ & 31])) {}
6  ...
7}

The error was found through the diagnostic: 'm_nCurrentBitIndex' 变量被修改并在单个时序点中被使用了两次.MACLib unbitarrayold.cpp 78

 'm_nCurrentBitIndex' 变量的使用在两者间并没有时序点. 意味着标准并未指定该变量何时增长. 情况不同, 该代码可能工作方式不同,依赖于编译器和优化选项.

例 4. 项目. 复杂的表达式.

1short ezxml_internal_dtd(ezxml_root_t root,
2  char *s, size_t len)
3{
4  ...
5  while (*(n = ++s + strspn(s, EZXML_WS)) && *n != '>') {
6  ...
7}

The error was found through the diagnostic: 未定义行为. 's' 变量被修改且在时序点间使用了两次. msne zxml.c

这里使用了前缀++. 但不代表什么:无法保证 's' 变量值会在 strspn() 函数调用前增长。

为了更容易理解例子,让我们先回顾运算符优先级列表。

例 1. 项目。! 和 & 运算的优先级

1int ha_innobase::create(...)
2{
3  ...
4  if (srv_file_per_table
5      && !mysqld_embedded
6      && (!create_info->options & HA_LEX_CREATE_TMP_TABLE)) {
7  ...
8}

The error was found through the diagnostic: '&' 操作被用到了bool类型的值上. 你很可能忘记加入圆括号或有意的使用'&&'操作。innobase ha_innodb.cc 6789

程序员想用表达式的某部分来检查 'create_info->options' 变量中的某个特定的比特位等于是否为 0 . 但 '!' 操作的优先级高于 '&' 操作,这就是该表达式使用下面运算规则的原因:

1((!create_info->options) & HA_LEX_CREATE_TMP_TABLE)
2We should use additional parentheses if we want the code to work properly:
3(!(create_info->options & HA_LEX_CREATE_TMP_TABLE))

或者,我们发现更好的方式,用下面的方式编写代码:

1((create_info->options & HA_LEX_CREATE_TMP_TABLE) == 0)

例 2. 项目. * 和 ++ 优先级.

1STDMETHODIMP
2CCustomAutoComplete::Next(..., ULONG *pceltFetched)
3{
4  ...
5  if (pceltFetched != NULL)
6    *pceltFetched++;
7  ...
8}

The error was found through the diagnostic:考虑审查 '*pointer++' 部分. 可能的意思是:'(*pointer)++'. emule customautocomplete.cpp 277

如果 'pceltFetched' 不是一个空指针,该函数必须增加该指针指向的ULONG类型变量的值。错误是:'++' 运算符的优先级高于 '*' 运算符的优先级(指针解引用)。该 "*pceltFetched++;" 行等同于下面的代码:

1TMP = pceltFetched + 1;
2*pceltFetched;
3pceltFetched = TMP;

实际上它仅仅增加了指针的值。为使代码正确,我们必须添加括号:"(*pceltFetched)++;".

例 3. 项目。& 和 != 运算符的优先级

1#define FILE_ATTRIBUTE_DIRECTORY 0x00000010
2 
3bool GetPlatformFileInfo(PlatformFile file, PlatformFileInfo* info) {
4  ...
5  info->is_directory =
6    file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0;
7  ...
8}

The error was found through the diagnostic: '&' 操作被用到了bool类型的值上. 你很可能忘记加入圆括号或有意的使用'&&'操作。base platform_file_win.cc 216

程序员们很容易忘记 '!=' 的优先级高于 '&' 的优先级。这就在我们的例子中发生了。因此,我们使用下面的表达式:

1info->is_directory =
2 file_info.dwFileAttributes & (0x00000010 != 0);

让我们简化它:

1info->is_directory = file_info.dwFileAttributes & (true);

再次简化它:

1info->is_directory = file_info.dwFileAttributes & 1;

原来, 我们是测试了第一个比特位而不是第5个比特位. 为修正它,我们需要添加圆括号。

例 4.BCmenu 项目。IF 和 ELSE 混乱。

1void BCMenu::InsertSpaces(void)
2{
3  if(IsLunaMenuStyle())
4    if(!xp_space_accelerators) return;
5  else
6    if(!original_space_accelerators) return;
7  ...
8}

The error was found through the diagnostic: 可能,这里的 'else' 分支必须与前一个 'if' 关联。 fire bcmenu.cpp 1853

这不是一个优先级的错误,但与它有关。程序员没有顾及 'else' 分支是于最近的 'if' 操作符关联。我们有充分的理由认为代码將以下面的算法工作:

1if(IsLunaMenuStyle()) {
2  if(!xp_space_accelerators) return;
3} else {
4  if(!original_space_accelerators) return;
5}

但实际上它等同于下面的结构:

1if(IsLunaMenuStyle())
2{
3   if(!xp_space_accelerators) {
4     return;
5   } else {
6     if(!original_space_accelerators) return;
7   }
8}

例 5.. ?: 和 | 的优先级。

1vm_file* vm_file_fopen(...)
2{
3  ...
4  mds[3] = FILE_ATTRIBUTE_NORMAL |
5           (islog == 0) ? 0 : FILE_FLAG_NO_BUFFERING;
6  ...
7}

The error was found through the diagnostic: 可能 '?:' 操作不会像期望的那样工作。'?:' 操作具有比 '|' 操作更低的优先级。 vm vm_file_win.c 393

依赖于 'islog' 变量值,该表达式必须等于 "FILE_ATTRIBUTE_NORMAL" 或 "FILE_ATTRIBUTE_NORMAL"。但这不会发生。'?:' 的优先级比 '|' 优先级低。因此,该代码会变成下面这样:

1mds[3] = (FILE_ATTRIBUTE_NORMAL | (islog == 0)) ?
2 0 : FILE_FLAG_NO_BUFFERING;

让我们简化该表达式:

1mds[3] = (0x00000080 | ...) ? 0 : FILE_FLAG_NO_BUFFERING;

既然 FILE_ATTRIBUTE_NORMAL等于0x00000080,该条件永远为真。意味着 0 总是被写入 mds[3] 中。

例 6. 项目。?: 和 * 的优先级。

1dgInt32 CalculateConvexShapeIntersection (...)
2{
3  ...
4  den = dgFloat32 (1.0e-24f) *
5        (den > dgFloat32 (0.0f)) ?
6          dgFloat32 (1.0f) : dgFloat32 (-1.0f);
7  ...
8}

The error was found through the diagnostic: 可能 '?:' 操作不会像期望的那样工作。'?:' 具有比 '*' 运算更低的优先级。physics dgminkowskiconv.cpp 1061

该代码的错误再一次与 '?:' 去处符相关。'?:' 运算符的条件被一个无效的表达式 "dgFloat32 (1.0e-24f) * (den > dgFloat32 (0.0f))"表示了. 添加圆括号將解决该问题.

顺便说明,程序员们常常忘记 '?:' 运算符是多么狡猾。这里有个关于该主题贴子:"How to make fewer errors at the stage of code writing. ".

列举这种类型的错误是重复而乏味的,因此我们將只检查少数几个例子。关键点是,函数声明的可变参数数量与实际接受的格式化字符串的参数数量不符. 任何使用如 printf() 这类函数函数的程序员所犯的错误都相似。

例 1. 项目. 不正确的打印WCHAR-character。

1static void REGPROC_unescape_string(WCHAR* str)
2{
3  ...
4  default:
5    fprintf(stderr,
6      "Warning! Unrecognized escape sequence: \\%c'\n",
7      str[str_idx]);
8  ...
9}

The error was found through the diagnostic: 不正确的格式化。考虑检查 'fprintf' 函数的的第三个实参。它期望一个字符类型的参数。

fprintf() 函数必须以char类型打印字符。但是第三个参数是WCHAR type类型的字符。用户將得到一个错误生成的消息。为修正该代码,我们需要在字符串格式化中將'%c'换成'%C'。

例 2. Intel AMT SDK 项目. 字符 '%' 丢失

01void addAttribute(...)
02{
03  ...
04  int index = _snprintf(temp, 1023,
05    "%02x%02x:%02x%02x:%02x%02x:%02x%02x:"
06    "%02x%02x:02x%02x:%02x%02x:%02x%02x",
07    value[0],value[1],value[2],value[3],value[4],
08    value[5],value[6],value[7],value[8],
09    value[9],value[10],value[11],value[12],
10    value[13],value[14],value[15]);
11  ...
12}

The error was found through the diagnostic: 不正确的格式化。当调用 'printf' 时, 实际参数数量和预期值不同. 期望:18.实际:19.

咋看之下很难发现错误。然而,PVS-Studio分析器并未疲于应对和通知,该函数携带了更多实际参数比指定的字符串格式化参数。原因是,'%'已在某个地方丢失了。让我们指出该片段:

1"%02x%02x:[HERE]02x%02x:%02x%02x:%02x%02x",

例 3. Intel AMT SDK 项目. 未使用的参数.

1bool GetUserValues(...)
2{
3  ...
4  printf("Error: illegal value. Aborting.\n", tmp);
5  return false;
6}

The error was found through the diagnostic: 不正确的格式化。当调用 'printf' 时, 实际参数数量和预期值不同. 期望:1.实际:2. RemoteControlSample remotecontrolsample.cpp 792

这里的错误是:当打印消息时, 'tmp' 变量未以任何方式使用。

例 4. 项目. 打印无效数据

01class Matrix3 {
02  ...
03  inline float* operator[] (int iRow) {
04  ...
05};
06void AnyVal::serialize(G3D::TextOutput& t) const {
07  ...
08  const Matrix3& m = *(Matrix3*)m_value;
09  ...
10  t.printf("%10.5f, %10.5f, %10.5f,\n
11           %10.5f, %10.5f, %10.5f,\n
12           %10.5f, %10.5f, %10.5f)",
13           m[0, 0], m[0, 1], m[0, 2],
14           m[1, 0], m[1, 1], m[1, 2],
15           m[2, 0], m[2, 1], m[2, 2]);
16  ...
17}

The error was found through the diagnostic:  逗号操作符 ',',放在数组了下标表达式中 '[0,0]'. graphics3D anyval.cpp 275

该程序打印无效值而不是矩阵值。当你使用不同的编程语言工作时, 你可能会编写类似的代码,并且有时候会忘记怎么在C语言中访问二维数组的元素。

让我们看看 'm[0,1]' 是怎样工作的。首先,表达式"0,1" 被运算。表达式结果是 1 。然后,'operator[]' 函数在 Matrix3 class中被调用. 函数取得实参值 1 并返回指向矩阵中的第一个字符串的指针. 

下面是正确代码:

1t.printf("%10.5f, %10.5f, %10.5f,\n
2        %10.5f, %10.5f, %10.5f,\n
3        %10.5f, %10.5f, %10.5f)",
4        m[0][0], m[0][1], m[0][2],
5        m[1][0], m[1][1], m[1][2],
6        m[2][0], m[2][1], m[2][2]);



很多的编程错误由打印错误导致。大多数这类错误能很快的在测试阶段的早期发现。也有一些这类错误被保留在代码中很长时间, 为开发者和用户制造麻烦.

你可以借助PVS-Studio分析器少犯这样的错误。它將在测试前发现这些错误,这将会显助减少缺陷阱和排错时间。

例 1. 项目. 在 IF 中赋值

1void CIcqProto::handleUserOffline(BYTE *buf, WORD wLen)
2{
3  ...
4  else if (wTLVType = 0x29 && wTLVLen == sizeof(DWORD))
5  ...
6}

The error was found through the diagnostic: 条件的某部分总为 true : 0x29. icqoscar8 fam_03buddy.cpp 632

由于错误打印,在 if 条件中的发生了赋值。正确的条件:"if (wTLVType == 0x29 && wTLVLen == sizeof(DWORD))".

例 2. 项目. 赋值错误

1BOOL WINAPI GetMenuItemInfoA(...)
2{
3  ...
4  mii->cch = mii->cch;
5  ...
6}

The error was found through the diagnostic: 'mii->cch' 变量值被赋值给了本身. user32 menu.c 4347

变量值被赋给了本身。程序员显然是想写成:"mii->cch = miiW->cch;".

例 3. 项目. 对象名打印错误

1static Value *SimplifyICmpInst(...) {
2  ...
3  case Instruction::Shl: {
4    bool NUW =
5      LBO->hasNoUnsignedWrap() && LBO->hasNoUnsignedWrap();
6    bool NSW =
7      LBO->hasNoSignedWrap() && RBO->hasNoSignedWrap();
8  ...
9}

The error was found through the diagnostic: 在 '&&' 操作的左边和右边有个相同的子表达式 'LBO->hasNoUnsignedWrap ()'. LLVMAnalysis instructionsimplify.cpp 1891

这是一个使用相似变量名导致的打印错误。在第一行,LBO 和 RBO 都必须使用。正确代码:

1bool NUW = LBO->hasNoUnsignedWrap() && RBO->hasNoUnsignedWrap();

例 4. 项目. 不正确的状态测试

1bool _isPointXValid;
2bool _isPointYValid;
3...
4bool isPointValid() {
5  return _isPointXValid && _isPointXValid;
6};

The error was found through the diagnostic:在 '&&' 操作的左边和右边有个相同的子表达式 '_isPointXValid && _isPointXValid'

'_isPointXValid' 名被使用了两次。该函数实际返回的代码应是:"_isPointXValid && _isPointYValid".

例 5. 项目. 未成功检查 \r\n.

1static void getContentLengthAndHeaderLength(...)
2{
3  ...
4  while(line[linelen] != '\r' && line[linelen] != '\r')
5  ...
6}

The error was found through the diagnostic:  在 '&&' 操作的左右两边两个相同的子表达式 'line [linelen] != '\r''. miniupnpc miniupnpc.c 153

因为打印错误,字符 '\r' 被检查了两次。实际上字符 '\n' 也要被检查。

例 6. 项目. 右括号放错位置.

1bool Matrix4::operator==(const Matrix4& other) const {
2  if (memcmp(this, &other, sizeof(Matrix4) == 0)) {
3    return true;
4  }
5  ...
6}

The error was found through the diagnostic: 'memcmp' 函数处理了 '0' 个元素。审查第三个参数。graphics3D matrix4.cpp 269

某个右括号放错位置。事实证明被比较的内存区域大小值通过"sizeof(Matrix4) == 0"表达计算了。该表达式总为 'false' 值。接着 'false' 转型为一个整型值 0 。正确的代码:

1if (memcmp(this, &other, sizeof(Matrix4)) == 0) {

例 7. . 错误的结构体成员拷贝。

01PassRefPtr
02Structure::getterSetterTransition(Structure* structure)
03{
04  ...
05  transition->m_propertyStorageCapacity =
06    structure->m_propertyStorageCapacity;
07  transition->m_hasGetterSetterProperties =
08    transition->m_hasGetterSetterProperties;
09  transition->m_hasNonEnumerableProperties =
10    structure->m_hasNonEnumerableProperties;
11  transition->m_specificFunctionThrashCount =
12    structure->m_specificFunctionThrashCount;
13  ...
14}

The error was found through the diagnostic: 'transition->m_hasGetterSetterProperties' 变量被赋给了自身. QtScript structure.cpp 512

查看该代码不容易发现错误。但它是存在的。‘m_hasGetterSetterProperties’ 被拷贝给了自身。以下是正确代码:

1transition->m_hasGetterSetterProperties =
2 structure->m_hasGetterSetterProperties;

例 8. 项目. 多余的 sizeof 操作。

1PSECURITY_ATTRIBUTES GetNullACL(void)
2{
3  PSECURITY_ATTRIBUTES sa;
4  sa  = (PSECURITY_ATTRIBUTES)
5    LocalAlloc(LPTR, sizeof(SECURITY_ATTRIBUTES));
6  sa->nLength = sizeof(sizeof(SECURITY_ATTRIBUTES));
7  ...
8}

The error was found through the diagnostic: 有个奇怪的 sizeof()  'sizeof (SECURITY_ATTRIBUTES)' 表达式参数. libhttpd util_win32.c 115

属性 'nLength' 必须包含'SECURITY_ATTRIBUTES'结构体大小。这是一个代码中的打印错误:'sizeof' 操作被使用了两次。因此,属性 'nLength' 保存了 'size_t' 类的大小。以下是正确代码:

1sa->nLength = sizeof(SECURITY_ATTRIBUTES);

例 9. 项目. 重复的变量声明

1int iNesSaveAs(char* name)
2{
3  ...
4  fp = fopen(name,"wb");
5  int x = 0;
6  if (!fp)
7    int x = 1;
8  ...
9}

The error was found through the diagnostic:將值赋给 'x' 可能会更好,而不是重新声明. Previous daclaration: ines.cpp, line 960. fceuxines.cpp 962

变量 'x' 必须保存信息,无论某文件是否成功打开。由于一个打印错误,一个新 'x' 变量被创建和初始化为 1 并取代了现有值。下面是正确代码:

1if (!fp)
2 x = 1;

例 10. 项目. 使用了 && 操作 替代 &.

1TCHAR GetASCII(WPARAM wParam, LPARAM lParam)
2{
3  ...
4  result=ToAscii(wParam,
5    (lParam >> 16) && 0xff, keys,&dwReturnedValue,0);
6  ...
7}

The error was found through the diagnostic: 条件表达式的某部分总为 true: 0xff. notepadPlus babygrid.cpp 694

"(lParam >> 16) && 0xff"表达式无意义,总是等于 1 (true)。这里的打印错误是使用了 '&&' 替代 '&'。

例 11. 项目. 不完备的条件.

1inline bool IsValidChar(int c)
2{
3  return c == 0x9 || 0xA || c == 0xD || c >= 0x20 &&
4         c <= 0xD7FF || c >= 0xE000 && c <= 0xFFFD ||
5         c >= 0x10000 && c <= 0x10FFFF;
6}

The error was found through the diagnostic:条件表达式的某部分总为 true: 0xA. WinDjView xmlparser.cpp 45 False

该IsValidChar函数总是返回 'true'。比较语义在某个部分丢失了,缘于一个打印错误:"... || 0xA || ...".

例 12. 项目. 多余的分号.

01int settings_default(void)
02{
03  ...
04  for(i=0; i<16; i++);
05    for(j=0; j<32; j++)
06    {
07      settings.conversion.equalizer_bands.boost[i][j] = 0.0;
08      settings.conversion.equalizer_bands.preamp[i]   = 0.0;
09    }
10}

The error was found through the diagnostic: 'for' 操作后面有个多余的分号 ';'. settings.c 483

所有的 C 和 C++ 程序员都知道多余分号 ';'的危险性。不幸的是,该知识并为避免他们犯此类打印错误。这里有一个'for' 操作, 后面有多余的分号 ';',使该程序片段无法执行。

例 13. 项目. break 运算丢失.

01int QCleanlooksStyle::pixelMetric(...)
02{
03  ...
04  case PM_SpinBoxFrameWidth:
05    ret = 3;
06    break;
07  case PM_MenuBarItemSpacing:
08    ret = 6;
09  case PM_MenuBarHMargin:
10    ret = 0;
11    break;
12  ...
13}

The error was found through the diagnostic: 'ret'被成功赋值两次。这可能是个错误。检查行数: 3765, 3767. QtGui qcleanlooksstyle.cpp 3767

这是个经典的错误 - 'break' 在 'switch' 操作中丢失,我想你不需要任何评论了。

例 14. 项目. 赋值取代了比较

01int FindItem(...)
02{
03  ...
04  int ret;
05  ret=FindItem(hwnd,dat,hItem,
06               (struct ClcContact ** )&z,
07               (struct ClcGroup ** )&isv,NULL);
08  if (ret=0) {return (0);}
09  ...
10}

The error was found through the diagnostic: 在 'if' 条件表达式中有一个可疑的赋值: ret = 0. clist_mw clcidents.c 179

这是个条件判断 'if' 操作符中的打印错误:'==' 被写成了 '='。当某个值不存在时,函数將会错误的处理该情形。

例 15. 项目. 错误的下标

01struct AVS_MB_INFO
02{
03  ...
04  Ipp8u refIdx[AVS_DIRECTIONS][4];
05  ...
06};
07 
08void AVSCompressor::GetRefIndiciesBSlice(void){
09  ...
10  if (m_pMbInfo->predType[0] & predType)
11  {
12    m_refIdx[iRefNum] = m_pMbInfo->refIdx[dir][0];
13    iRefNum += 1;
14  }
15  if (m_pMbInfo->predType[1] & predType)
16  {
17    m_refIdx[iRefNum] = m_pMbInfo->refIdx[dir][1];
18    iRefNum += 1;
19  }
20  if (m_pMbInfo->predType[2] & predType)
21  {
22    m_refIdx[iRefNum] = m_pMbInfo->refIdx[dir][2];
23    iRefNum += 1;
24  }
25  if (m_pMbInfo->predType[3] & predType)
26  {
27    m_refIdx[iRefNum] = m_pMbInfo->refIdx[dir][30];
28    iRefNum += 1;
29  }
30  ...
31}

The error was found through the diagnostic: 数组可能越界. 下标 '30' 已指向数组边界外. avs_enc umc_avs_enc_compressor_enc_b.cpp 495

考虑该片段: "m_pMbInfo->refIdx[dir][30]". 由于一个打印错误,数字 30 被写成了 3 .顺便说下,该例子很好的说明了我们是怎样相对的將错误分类的. 该错误也能很好的与 "数组和字符串处理的错误" 分类关联. 分类只是相对的, 并展示PVS-Studio 分析器能发现的多种错误.

例 16. 项目. 宏定义中的打印错误

1#define SWAP(a,b,c)  c = a;\
2                    a = b;\
3                    a = c

The error was found through the diagnostic: 'v2'变量被成功赋值两次。这可能是个错误。检查行号: 343, 343. win32k gradient.c 343

这是个相当荒唐的宏定义打印错误,本意是想交换两个变量值。仔细观察代码,你就知道我的意思。下面是正确代码:

1#define SWAP(a,b,c)  c = a;\
2                    a = b;\
3                    b = c

例 17. 项目. 打印错误. 逗号取代了乘法操作。

01void Q1_AllocMaxBSP(void)
02{
03  ...
04  q1_allocatedbspmem +=
05    Q1_MAX_MAP_CLIPNODES * sizeof(q1_dclipnode_t);
06  ...
07  q1_allocatedbspmem +=
08    Q1_MAX_MAP_EDGES , sizeof(q1_dedge_t);
09  ...
10  q1_allocatedbspmem +=
11    Q1_MAX_MAP_MARKSURFACES * sizeof(unsigned short);
12  ...
13}

The error has been found with rule : 该表达式使用 ',' 操作符是危险的. 请确保该表达式正确. bspc l_bsp_q1.c 136

它是个荒唐的打印错误。检查中间的代码行。',' 取代了 '*'。因此,'sizeof(q1_dedge_t)'值总是添加给‘q1_allocatedbspmem’。对于该打印错误將发生什么,我无从得知。

例 18. LibXml 项目. 错误打印 =+.

1static int
2xmlXPathCompOpEvalFirst(...)
3{
4  ...
5  total += xmlXPathCompOpEvalFirst(...);
6  ...
7  total =+ xmlXPathCompOpEvalFilterFirst(ctxt, op, first);
8  ...
9}

The error has been found with rule : 使用了表达式 'A =+ B'. 考虑复查它, 可能是 'A += B'的意思. libxml xpath.c 12676

在某个地方,"=+" 错误的替代了 "+="。它们看起来看相似,但结果会非常不同。这类错误非常难以发现,只能复查代码。

很多软件错误是由于打印错误导致的。还有比程序员认为的更多这此类错误。我们可以在本章中继续列出, 但我们决定在列举第18个例子后结束战斗。

例 1. Fennec Media 项目. 需要两个末端 nulls.

01int JoiningProc(HWND hwnd,UINT uMsg,
02  WPARAM wParam,LPARAM lParam)
03{
04  ...
05  OPENFILENAME  lofn;
06  memset(&lofn, 0, sizeof(lofn));
07  ...
08  lofn.lpstrFilter = uni("All Files (*.*)\0*.*");
09  ...
10}

The error was found through the diagnostic: 'lpstrFilter'成员应当使用两个 0 字符指向字符串末端. base windows.c 5309

在 Windows 的API中,某些结构体的字符串成员指针必须以两个 null 字符结束. OPENFILENAME 结构体的成员'lpstrFilter' 指向的是一个非同寻常的字符串. 

MSDN中对 'lpstrFilter' 的描述

LPCTSTR

A buffer containing pairs of null-terminated filter strings. The last string in the buffer must be terminated by two NULL characters.

一个包含成对的 null-terminated 过滤字符串缓冲区。该缓冲区中的最后一个字符串必须以两个 NULL 字符结尾。

如果你忘记在末端添加上另一个 null ,文件处理的对话框可能会包含一些在过滤属性中的垃圾数据。

1lofn.lpstrFilter = uni("All Files (*.*)\0*.*\0");

例 2. 项目. 错误的使用 'remove' 函数。

1STDMETHODIMP CShellExt::Initialize(....)
2{
3  ...
4  ignoredprops = UTF8ToWide(st.c_str());
5  // remove all escape chars ('\\')
6  std::remove(ignoredprops.begin(), ignoredprops.end(), '\\');
7  break;
8  ...
9}

The error was found through the diagnostic: 'remove' 的返回值需要被有效利用. contextmenu.cpp 442

std::remove函数不会將容器内的元素移除。它仅仅反转元素和返回一个指向垃圾数据开始端的迭代器。假设我们有一个 vector 容器,包含元素 1,2,3,1,2,3,1,2,3。如果我们执行代码 "remove( v.begin(), v.end(), 2 )",该容器將包含元素 1,3,1,3,X,X,X,X是一些垃圾数据。该函数將返回一个指向第一个垃圾数据的迭代器,因此如果我们想移除这些垃圾元素,我们需要编写为: "v.erase(remove(v.begin(), v.end(), 2), v.end())".

例 3. 项目. 使用 'empty' 函数替代了 'clear'.

01CMailMsg& CMailMsg::SetFrom(string sAddress,
02                            string sName)
03{
04   if (initIfNeeded())
05   {
06      // only one sender allowed
07      if (m_from.size())
08         m_from.empty();
09      m_from.push_back(TStrStrPair(sAddress,sName));
10   }
11   return *this;
12}

The error was found through the diagnostic: 'empty' 的返回值需要被有效利用. mailmsg.cpp 40

这里的错误是:vector::empty()函数被错误的调用而取代 vectory::clear(),而该数组的内容保存不变。这是一个常见的错误,因为单词 'clear' 和 'empty' 意思非常的接近。

例 4. 项目. 使用了 'empty' 函数替代 'clear'。

01void CDirView::GetItemFileNames(int sel,
02  String& strLeft, String& strRight) const
03{
04  UINT_PTR diffpos = GetItemKey(sel);
05  if (diffpos == (UINT_PTR)SPECIAL_ITEM_POS)
06  {
07    strLeft.empty();
08    strRight.empty();
09  }
10  else
11  {
12     ...
13  }
14}

The error was found through the diagnostic: 'empty' 的返回值需要被有效利用. WinMerge DirActions.cpp 1307, 1308

再一次,使用了 empty() 函数替代 clear()。我们还可以从其它项目中引用这类错误示例: InstantVNC, IPP Samples, Chromium, Intel AMT SDK等等。遗憾的是,所有的这些例子都相同,我没有兴趣去检查他们。但请相信我,你也会在由专业程序员开发的工业级项目中看到此类缺陷。

例 5. 项目. 在循环中使用 'alloca'.

01inline  void  triangulatePolygon(...) {
02  ...
03  for (i=1;i
04    ...
05    do {
06      ...
07      do {
08        ...
09        CTriVertex  *snVertex =
10         (CTriVertex *)alloca(2*sizeof(CTriVertex));
11        ...
12      } while(dVertex != loops[0]);
13      ...
14    } while(sVertex != loops[i]);
15    ...
16  }
17  ...
18}

The error was found through the diagnostic: 在循环中使用 'alloca'. 这会很快使用栈溢出. ri polygons.cpp 1120

alloca 函数是在栈中申请内存,因此如果在循环中多次调用它的话,可能会突然导致栈溢出。而我们这里有几个嵌套循环。该代码將很快的耗尽栈内存。

例 6. 项目. 多参数混淆

1static BOOL ImageArray_Alloc(LP_IMAGE_ARRAY_DATA iad, int size)
2{
3  ...
4  memset(&iad->nodes[iad->nodes_allocated_size],
5    (size_grow - iad->nodes_allocated_size) *
6       sizeof(IMAGE_ARRAY_DATA_NODE),
7    0);
8  ...
9}

The error was found through the diagnostic:  函数接收一个多余的参数. clist_modern modern_image_array.cpp 59

'memset'函数处理 0 个无素,实际什么也没做。原因是参数混淆了。下面是调用 memset 函数的正确写法:

1memset(&iad->nodes[iad->nodes_allocated_size],
2  0,
3  (size_grow - iad->nodes_allocated_size) *
4     sizeof(IMAGE_ARRAY_DATA_NODE));

例 1. 项目. 不完备的条件

01void lNormalizeVector_32f_P3IM(Ipp32f *vec[3],
02  Ipp32s* mask, Ipp32s len)
03{
04  Ipp32s  i;
05  Ipp32f  norm;
06 
07  for(i=0; i
08    if(mask<0) continue;
09    norm = 1.0f/sqrt(vec[0][i]*vec[0][i]+
10             vec[1][i]*vec[1][i]+vec[2][i]*vec[2][i]);
11    vec[0][i] *= norm; vec[1][i] *= norm; vec[2][i] *= norm;
12  }
13}

The error was found through the diagnostic: 一个无意义的比较:pointer < 0 .ipprsample ippr_sample.cpp 501

I do not know how it happened, but there are 3 characters "[i]" missing in this code. As a result, the code performs a meaningless check that the pointer is below zero instead of checking the mask array.

我不知道它会发生什么,但该代码有 3 个字符 "[i]" 丢失了。因此,该代码执行了个无效的检查,指针为0 而不是检查 mask 数组。

正确的检查应该写成这样:if(mask[i] < 0).

例 2. Pc Ps2 Emulator 项目. 无效分支

01LRESULT CALLBACK IOP_DISASM(...)
02{
03  ...
04  switch(LOWORD(wParam))
05  {
06    case (IDOK || IDCANCEL):
07      EndDialog(hDlg,TRUE);
08      return(TRUE);
09      break;
10  }
11  ...
12}

The error was found through the diagnostic: 条件表达式的某部分总为 true. pcsx2 debugger.cpp 321

该代码没有任何意义。程序员一定是想写成这样的方式:

1switch(LOWORD(wParam))
2{
3  case IDOK: //no break
4  case IDCANCEL:
5    EndDialog(hDlg,TRUE);
6    return(TRUE);
7    break;
8}

例 3. CPU Identifying Tool 项目. 一个太严格的条件

1void projillum(short* wtab, int xdots, int ydots, double dec)
2{
3  ...
4  s = sin(-dtr(dec));
5  x = -s * sin(th);
6  y = cos(th);
7  ...
8  lon = (y == 0 && x == 0) ? 0.0 : rtd(atan2(y, x));
9}

The error was found through the diagnostic: 一个多余的条件比较:x == 0. 可能更好的比较方式是:fabs(A - B) '<'. Epsilon. clock_dll sunalgo.cpp 155

 令人奇怪的是,经过使用 'sin' 和 'cos' 这些复杂的运算函数, 最终的期望结果是 0. 可能这里必须要执行某个精确值的比较.

例 4. 项目. 重复赋值

1int Game::DrawGLScene(void)
2{
3  ...
4  radius=fast_sqrt(maxdistance);
5  radius=110;
6  ...
7}

The error was found through the diagnostic: 'radius' 对象被成功的赋值了两次. 这可能是个错误. Lugaru gamedraw.cpp 1505

程序员一定是为了试验, 故意的给 'radius' 写入 110, 然后忘记移除该行了. 因此,我们得到一个无用的甚至可能是无效的代码.

例 5. 项目. 重复检查。

01Q3TextCustomItem* Q3TextDocument::parseTable(...)
02{
03  ...
04  while (end < length
05    && !hasPrefix(doc, length, end, QLatin1String("
06    && !hasPrefix(doc, length, end, QLatin1String("
07    && !hasPrefix(doc, length, end, QLatin1String("
08    && !hasPrefix(doc, length, end, QLatin1String("
09    && !hasPrefix(doc, length, end, QLatin1String("
10    && !hasPrefix(doc, length, end, QLatin1String("
11    && !hasPrefix(doc, length, end, QLatin1String("
12    && !hasPrefix(doc, length, end, QLatin1String("
13 
14  ...
15}

The error was found through the diagnostic: 在 '&&' 操作的左边和右边有个相同的子表达式. Qt3Support q3richtext.cpp 6978

"

例 6. 项目. 怪异的检查.

01int sf_error (SNDFILE *sndfile)
02{
03  ...
04  if (!sndfile)
05  {
06    if (sf_error != 0)
07      return sf_errno;
08    return 0;
09  } ;
10  ...
11}

The error was found through the diagnostic: 考虑审核一个奇怪的表达式. 非空函数指针被用于与 null 比较:'sf_error != 0'。

"sf_error != 0" 检查总是返回 true,因为被执行的代码里 'sf_error' 是函数的名字.

例 7. 项目. 循环中怪异的代码

1static IppStatus mp2_HuffmanTableInitAlloc(Ipp32s *tbl, ...)
2{
3  ...
4  for (i = 0; i < num_tbl; i++) {
5    *tbl++;
6  }
7  ...
8}

The error was found through the diagnostic: 考虑审核一个奇怪的模式 '*pointer++' .可能意思是:'(*pointer)++'. mpeg2_dec umc_mpeg2_dec.cpp 59

循环体可能不完备因为在循环体中它是无效的.



这是一类非常广泛的错误类型。这些错误也很依赖于严重程度。不那么危险的错误,我们可能只是用ASSERT做不正确的断言,实际上并未检查任何东西。而对于危险的错误,是那些不正确的缓冲区检查或下标指向。

例 1. 项目. char类型的范围

01void CRemote::Output(LPCTSTR pszName)
02{
03 
04  ...
05  CHAR* pBytes = new CHAR[ nBytes ];
06  hFile.Read( pBytes, nBytes );
07  ...
08  if ( nBytes > 3 && pBytes[0] == 0xEF &&
09       pBytes[1] == 0xBB && pBytes[2] == 0xBF )
10  {
11    pBytes += 3;
12    nBytes -= 3;
13    bBOM = true;
14  }
15  ...
16}

The error was found through the diagnostic: 表达式 'pBytes [ 0 ] == 0xEF' 总为 false。无符号 char 类型的范围是: [-128, 127].

In this code, the 'TCHAR' type is the 'char' type. The value range of char is from -128 to 127 inclusive. Value 0xEF in the variable of the char type is nothing else than number -17. When comparing the char variable with number 0xEF, its type is extended up to the 'int' type. But the value still lies inside the range [-128..127]. The "pBytes[0] == 0xEF" ("-17 == 0xEF") condition is always false, and the program does not work as intended.

在该代码中,'TCHAR' 是 'char' 类型。char的值范围从 -128 到 127 。值0xEF 在char 类型中只不过是 -17。当比较 char 变量与数字 0xEF时,它的类型被向上转型为 'int' 类型。但该值仍处于 [-128..12]区间中。"pBytes[0] == 0xEF" ("-17 == 0xEF")条件总为 false,程序不会按照意图那样工作。

下面是正确的比较:

1if ( nBytes > 3 && pBytes[0] == TCHAR(0xEF) &&
2                  pBytes[1] == TCHAR(0xBB) &&
3                  pBytes[2] == TCHAR(0xBF) )

例 2. 项目. char类型的范围。

01BOOL TortoiseBlame::OpenFile(const TCHAR *fileName)
02{
03  ...
04  // check each line for illegal utf8 sequences.
05  // If one is found, we treat
06  // the file as ASCII, otherwise we assume
07  // an UTF8 file.
08  char * utf8CheckBuf = lineptr;
09  while ((bUTF8)&&(*utf8CheckBuf))
10  {
11    if ((*utf8CheckBuf == 0xC0)||
12        (*utf8CheckBuf == 0xC1)||
13        (*utf8CheckBuf >= 0xF5))
14    {
15      bUTF8 = false;
16      break;
17    }
18 
19   ...
20  }
21  ...
22}

The error was found through the diagnostic: 表达式 '* utf8CheckBuf == 0xC0' 总为 false. 无符号 char 类型的范围是: [-128, 127]. tortoiseblame.cpp 310

While the defect in the previous example seems to be caused through mere inattention, in this case it is not so. Here is another identical example where a condition is always false. This is a very widely-spread type of errors in various projects.

上个例子看起是由于粗心导致, 但这里并非如此。这是另一个相同的例子,条件总为 false。这是一个非常广泛的错误类型见于各类项目中。

例 3. 项目. 无符号类型总是 >=0.

1typedef unsigned short wint_t;
2...
3void lexungetc(wint_t c) {
4  if (c < 0)
5    return;
6   g_backstack.push_back(c);
7}

The error was found through the diagnostic: 表达式 'c < 0' 总为 false. 无效号类型的值不可能 < 0. Ami lexer.cpp 225

"c < 0" 条件总为 false ,因为无符号数的变量总是大于或等于0.

例 4.  项目. Socket处理.

01static UINT_PTR m_socketHandle;
02 
03void TTrace::LoopMessages(void)
04{
05  ...
06  // Socket creation
07  if ( (m_socketHandle = socket(AF_INET,SOCK_STREAM,0)) < 0)
08  {
09    continue;
10  }
11  ...
12}

The error was found through the diagnostic: 表达式 '(m_socketHandle = socket (2, 1, 0)) < 0' 总为 false。无符号类型的值从不 <0 . Vs8_Win_Lib tracetool.cpp 871

一个企图检查socket是否成功创建被错误的执行了. 如果socket无法创建,这种情况没有被任何方式处理。为使检查工作正确,我们应使用

INVALID_SOCKET 常量:

1m_socketHandle = socket(AF_INET,SOCK_STREAM,0);

例 5. 项目. 时间处理

01IdleState CalculateIdleState(...) {
02  ...
03  DWORD current_idle_time = 0;
04  ...
05  // Will go -ve if we have been idle for
06  // a long time (2gb seconds).
07  if (current_idle_time < 0)
08    current_idle_time = INT_MAX;
09  ...
10}

The error was found through the diagnostic: 表达式 'current_idle_time < 0' 总为 false. 无符号类型的值从不 <0. 浏览 idle_win.cc 23

为处理时间,使用了个无符号类型变量。因此,太大的值检查不能工作。这是正确代码:

1if (current_idle_time > INT_MAX)
2 current_idle_time = INT_MAX;

例 6. ICU 项目. 条件出错

01U_CDECL_BEGIN static const char* U_CALLCONV
02_processVariableTop(...)
03{
04  ...
05  if(i == locElementCapacity &&
06     (*string != 0 || *string != '_'))
07  {
08    *status = U_BUFFER_OVERFLOW_ERROR;
09  }
10  ...
11}

The error was found through the diagnostic: 表达式 "*string != 0 || *string != '_'" 总为 true. 可能这里应使用 '&&' 操作符. icui18n ucol_sit.cpp 242

条件包含了逻辑错误。"(*string != 0 || *string != '_')" 子表达式总为 true。同一个string字符不可能同时等于 '0' 和 '_'。

例 7. 项目. 危险的循环

01bool equals( class1* val1, class2* val2 ) const{
02{
03  ...
04  size_t size = val1->size();
05  ...
06  while ( --size >= 0 ){
07    if ( !comp(*itr1,*itr2) )
08      return false;
09    itr1++;
10    itr2++;
11  }
12  ...
13}

The error was found through the diagnostic: 表达式 '--size >= 0' 总为 true. 无符号类型的值总是 >=0. QtCLucene arrays.h 154

The (--size >= 0) condition is always true, since the size variable has the unsigned type. It means that if two sequences being compared are alike, we will get an overflow that will in its turn cause Access Violation or other program failures.

(--size >= 0) 条件总为 true,既然变量的大小是无符号类型. 意味着, 如果两个相同序列被比较,我们將得到溢出, 它將导致Access Violation 或其它的软件错误.

这是正确代码:

1for (size_t i = 0; i != size; i++){
2  if ( !comp(*itr1,*itr2) )
3    return false;
4  itr1++;
5  itr2++;
6}

例 8. 项目. 条件出错

1enum enum_mysql_timestamp_type
2str_to_datetime(...)
3{
4  ...
5  else if (str[0] != 'a' || str[0] != 'A')
6    continue; /* Not AM/PM */
7  ...
8}

The error was found through the diagnostic:  表达式 "str [0] != 'a' || str [0] != 'A'" 总为 true. 可能这里应该用 '&&' 操作符. clientlib my_time.c 340

条件总为 true,因此字符即不等于 'a' 也不等于 'A'.这是正确代码:

1else if (str[0] != 'a' && str[0] != 'A')

例 9. 项目. 不正确的引用计数

1STDMETHODIMP QEnumPins::QueryInterface(const IID &iid,void **out)
2{
3  ...
4  if (S_OK)
5    AddRef();
6  return hr;
7}

The error was found through the diagnostic: Such conditional expression of 'if' operator is incorrect for the HRESULT type value '(HRESULT) 0L'. The SUCCEEDED or FAILED macro should be used instead. phonon_ds9 qbasefilter.cpp 60

这个 'if' 表达式操作是不正确的,因为是HRESULT 类型的值 value '(HRESULT) 0L'. 应该用 SUCCESSED 或 FAILED 宏指令替换.

检查被 S_OK 代表了. 既然 S_OK 是 0,AddRef()函数將会从未被调.正确的检查必须是这样:if (hr == S_OK).

例 10 . TickerTape 项目. Incorrect tornado.

01void GetWindAtSingleTornado(...)
02{
03  ...
04  if(radius < THRESH * 5)
05      *yOut = THRESH * 10 / radius;
06  else if (radius < THRESH * 5)
07      *yOut = -3.0f / (THRESH * 5.0f) *
08             (radius - THRESH * 5.0f) + 3.0f;
09  else
10      *yOut = 0.0f;
11  ...
12}

The error was found through the diagnostic:一个使用了 'if (A) {...} else if (A) {...}' 的模式被查出. 这可能有一个逻辑错误. TickerTape wind.cpp 118

第二个条件总为 false. 原因是第一个条件与第二个条件恰恰相同. 这肯定是个打印错误.

例 11. 项目. Windows中的 socket 处理错误

01typedef UINT_PTR SOCKET;
02 
03static unsigned int __stdcall win9x_accept(void * dummy)
04{
05  SOCKET csd;
06  ...
07  do {
08      clen = sizeof(sa_client);
09      csd = accept(nsd, (struct sockaddr *) &sa_client, &clen);
10  } while (csd < 0 && APR_STATUS_IS_EINTR(apr_get_netos_error()));
11  ...
12}

The error was found through the diagnostic: 表达式 'csd < 0' 总为 false. 无符号类型值从不 <0. libhttpd child.c 404

Socket handling errors very often emerge in crossplatform programs built under Windows. In Linux, socket descriptors are represented by the signed type, while in Windows it is the unsigned type. Programmers often forget about this and check the error status by comparing the value to 0. This is incorrect; you must use specialized constants.

构建于Windows平台下的Socket错误处理常常破坏程序的跨平台性. 在Linux中,socket描述符常常使用有符号类型代表,而在Windows中是无符号类型.程序员们常常忘记,而常常使用 =0 判断错误状态.这是不正确的;你必须使用特殊的常量.

例 12. 项目. 比较中的打印错误

01QStringList ProFileEvaluator::Private::values(...)
02{
03  ...
04  else if (ver == QSysInfo::WV_NT)
05    ret = QLatin1String("WinNT");
06  else if (ver == QSysInfo::WV_2000)
07    ret = QLatin1String("Win2000");
08  else if (ver == QSysInfo::WV_2000)  <<--
09    ret = QLatin1String("Win2003");
10  else if (ver == QSysInfo::WV_XP)
11    ret = QLatin1String("WinXP");
12  ...
13}

The error was found through the diagnostic: 被查出使用了 'if (A) {...} else if (A) {...}' 模式. 这可能是个逻辑错误. 查检行号: 2303, 2305. lrelease profileevaluator.cpp 2303

字符串中我们已标记了它必须是 "ver == QSysInfo::WV_2003" .由于该错误,"ret = QLatin1String("Win2003")"块將会从不被执行.

当然, 代码漏洞实际由打印错误, 不正确的条件和不正确的数组操作导致. 但我们决定特别的分类指出这些错误, 因为它们与软件漏洞相关. 入侵者,利用这些错误,能试图破坏程序执行, 获取特别权限执行攻击或任何他/她需要的操作.

例 1. 项目. 不正确的检查空字符串

01char *CUT_CramMd5::GetClientResponse(LPCSTR ServerChallenge)
02{
03  ...
04  if (m_szPassword != NULL)
05  {
06    ...
07    if (m_szPassword != '\0')
08    {
09  ...
10}

The error was found through the diagnostic: 奇怪的 'char' 类型指针与 '\0' 比较.  可能意思是: *m_szPassword != '\0'. UTMail ut_crammd5.cpp 333

该代码片断必须检查指向 password 的指针不为 NULL 且该字符串不为空.但替代,该代码对指针不为 NULL 检查了两次. s字符串的检查无法工作. "if (m_szPassword != '\0')" 条件意图检查在字符串最开始处是否有一个终止符 null, 意思是字符串为空. 但这里缺少指针解引用操作,仅仅是指针自己和 0 比较. 下面是正确代码:

1if (m_szPassword != NULL)
2{
3  ...
4  if (*m_szPassword != '\0')

例 2. 项目. 空指针操作.

1bool ChromeFrameNPAPI::Invoke(...)
2{
3  ChromeFrameNPAPI* plugin_instance =
4    ChromeFrameInstanceFromNPObject(header);
5  if (!plugin_instance &&
6      (plugin_instance->automation_client_.get()))
7    return false;
8  ... 
9}

The error was found through the diagnostic: 可能发生对空指针 plugin_instance 解引用. 请检查逻辑条件. chrome_frame_npapi chrome_frame_npapi.cc 517

检查空指针的条件被不正确的编写. 因此,我们得到了一个 . 下面是正确代码:

1if (plugin_instance &&
2   (plugin_instance->automation_client_.get()))
3 return false;

例 3. 项目. 不完全缓冲区清理.

1void MD5::finalize () {
2  ...
3  uint1 buffer[64];
4  ...
5  // Zeroize sensitive information
6  memset (buffer, 0, sizeof(*buffer));
7  ...
8}

The error was found through the diagnostic: 'memset' 函数的调用將导致缓冲区上溢或下溢. CSmtp md5.cpp 212

出于安全考虑,该函数试图清理包含些敏感信息的缓冲. 但它失败了.仅仅缓冲的第一个字节被清除.错误是: 'sizeof' 操作计算了 'uint1' 类型的大小而非缓冲大小. 这是正确代码:

1memset (buffer, 0, sizeof(buffer));

通常,不完全的内存清理常常发生. 请认真考虑其它的类似情况.

例 4. . 不完全缓冲区清理.

1void Time::Explode(..., Exploded* exploded) const {
2  ...
3  ZeroMemory(exploded, sizeof(exploded));
4  ...
5}

The error was found through the diagnostic: 'memset' 函数的调用將导致缓冲区上溢或下溢. base time_win.cc 227

ZeroMemory 函数仅仅清理Exploded结构体的一部分.原因是 'sizeof' 操作符返回的是指针大小.修复该错误,我们必须解引用指针:

1ZeroMemory(exploded, sizeof(*exploded));

例 5. 项目. 不完全缓冲区清理.

1#define MEMSET_BZERO(p,l)       memset((p), 0, (l))
2 
3void apr__SHA256_Final(..., SHA256_CTX* context) {
4  ...
5  MEMSET_BZERO(context, sizeof(context));
6  ...
7}

The error was found through the diagnostic: 'memset' 函数的调用將导致缓冲区 '(context)' 上溢或下溢. apr sha2.c 560

该错误与前例完全相同. 'sizeof' 计算的是指针大小.改正他,我们必须写:"sizeof(*context)".

例 6. 项目. 不正确的字符串处理

1static char *_skipblank(char * str)
2{
3  char * endstr=str+strlen(str);
4  while ((*str==' ' || *str=='\t') && str!='\0') str++;
5  while ((*endstr==' ' || *endstr=='\t') &&
6         endstr!='\0' && endstr
7    endstr--;
8  ...
9}

The error was found through the diagnostics 奇怪的 'char' 类型指针与 '\0' 比较. 可能意思是:*str != '\0'. clist_modern modern_skinbutton.cpp 282

该代码非常危险,因为它不正确的定义了字符串结尾. 它可能导致字符串溢出,以一个 Access Violation 异常为代价. 错误在这里:"str!='\0'" 和这里:"endstr!='\0'". 指针解引用丢失了. 这是正确代码:

1while ((*str==' ' || *str=='\t') && *str!='\0') str++;
2while ((*endstr==' ' || *endstr=='\t') &&
3       *endstr!='\0' && endstr
4  endstr--;

例 7. 项目. 意外的指针清除

01png_size_t
02png_check_keyword(png_structp png_ptr, png_charp key,
03  png_charpp new_key)
04{
05  ...
06  if (key_len > 79)
07  {
08    png_warning(png_ptr, "keyword length must be 1 - 79 characters");
09    new_key[79] = '\0';
10    key_len = 79;
11  }
12  ...
13}

The error was found through the diagnostic:奇怪的將 '\0' 赋给一个 'char' 类型的指针. 可能意思:*new_key [79] = '\0'. graphics3D pngwutil.c 1283

该例子展示了一种错误,当程序员不小心清理了指针而不是截断字符串长度. 'new_key' 是指向字符串的指针. 这意味着我们应该像下面这样编写我们的代码,將它截断到 79 个字符:

1(*new_key)[79] = '\0';

例 8. Intel AMT SDK 项目. 未验证用户名.

1static void
2wsman_set_subscribe_options(...)
3{
4  ...
5  if (options->delivery_certificatethumbprint ||
6     options->delivery_password ||
7     options->delivery_password) {
8  ...
9}

The error was found through the diagnostic: 在 '||' 操作符的左边和右边有两个相同的子表达式 'options->delivery_password'. OpenWsmanLib wsman-client.c 631

由于开发者的粗心,password 被检查了两次,而用户名未被检查.这是正确代码:

1if (options->delivery_certificatethumbprint ||
2  options->delivery_username ||
3  options->delivery_password) {

例 9. 项目. 不正确的处理空字符串.

01void CUT_StrMethods::RemoveCRLF(LPSTR buf)
02{
03  // v4.2 changed to size_t
04  size_t  len, indx = 1;
05  if(buf != NULL){
06    len = strlen(buf);
07    while((len - indx) >= 0 && indx <= 2) {
08      if(buf[len - indx] == '\r' ||
09         buf[len - indx] == '\n')
10         buf[len - indx] = 0;
11      ++indx;
12    }
13  }
14}

The error was found through the diagnostic: 表达式 '(len - indx) >= 0' 总为 true.无符号类型总是 >=0 . UTDns utstrlst.cpp 58

"len - indx" 表达式是无符号类型 'size_t' , 它总是 >=0 . 让我们看看如果发送个空字符串,会返回什么.

如果字符串为空,则:len = 0, indx = 1.

len - indx 表达式等于 0xFFFFFFFFu.

既然 0xFFFFFFFFu > 0 而 indx <=2, 一个数组访问被执行

"buf[len - indx]".

"buf[0xFFFFFFFFu]" 操作將导致 Access Violation.

例 10. 项目. 溢出保护不起作用

01void Append( PCXSTR pszSrc, int nLength )
02{
03  ...
04  UINT nOldLength = GetLength();
05  if (nOldLength < 0)
06  {
07    // protects from underflow
08    nOldLength = 0;
09  }
10  ...
11}

The error was found through the diagnostic: 表达式 'nOldLength < 0' 总为 true. 无符号数从不 <0 . IRC mstring.h 229

"if (nOldLength < 0)"的检查不起作用, 因为 nOldLength 是无符号类型.

例 11. 项目. 不正确的处理负数.

01typedef  size_t      apr_size_t;
02APU_DECLARE(apr_status_t) apr_memcache_getp(...)
03{
04  ...
05  apr_size_t len = 0;
06  ...
07  len = atoi(length);
08  ...
09  if (len < 0) {
10    *new_length = 0;
11    *baton = NULL;
12  }
13  else {
14    ... 
15  }
16}

The error was found through the diagnostic:表达式 'len < 0' 总为 false. 无符号数从不 <0 . aprutil apr_memcache.c 814

"if (len < 0)" 检查不起作用, 'len' 变量是无符号类型.

例 12. 项目. 不正确的循环终止条件

01void CUT_StrMethods::RemoveSpaces(LPSTR szString) {
02  ...
03  size_t loop, len = strlen(szString);
04  // Remove the trailing spaces
05  for(loop = (len-1); loop >= 0; loop--) {
06    if(szString[loop] != ' ')
07      break;
08  }
09  ...
10}

The error was found through the diagnostic: 表达式 'loop >= 0' 总为 true. 无符号数总是 >=0. UTDns utstrlst.cpp 430

Suppose the whole string consists only of spaces. While searching the characters, the program will reach the null item of the string, and the 'loop' variable will equal to zero. Then it will be decremented once again. Since this variable is of unsigned type, its value will be 0xFFFFFFFFu or 0xFFFFFFFFFFFFFFFFu (depending on the architecture). This value is 'naturally >= 0', and a new loop iteration will start. There will be an attempt of memory access by szString[0xFFFFFFFFu] address - the consequences of this are familiar to every C/C++ programmer.

假设整个字符串只包含空格. 当搜索字符的时候,程序到达字符串的 null 元素,并且 'loop' 变量等于 0. 然后再次递减. 既然变量是无符号类型,它的值將是 0xFFFFFFFFu 或 0xFFFFFFFFFFFFFFFFu (依赖于体系结构).该表达式为 'naturally >= 0',这将会启动一个新循环. 有个操作试图通过szString[0xFFFFFFFFu]地址的内存访问 -这样的后果是每一个C/C++程序员所熟知的.

例 13. 项目. 基本类型数据清除错误.

1void CAST256::Base::UncheckedSetKey(const byte *userKey,
2  unsigned int keylength, const NameValuePairs &)
3{
4  AssertValidKeyLength(keylength);
5  word32 kappa[8];
6  ...
7  memset(kappa, 0, sizeof(kappa));
8}

The error has been found with rule : 编译器可以删除 'memset' 函数调用, 这是用来清洗 'kappa' 缓冲区.RtlSecureZeroMemory() 函数应被用来擦除基本类型数据. cryptlib cast.cpp 293

问题出在 memset() 函数中. 传递给函数的参数是正确的. 如果开发者在调试器中查看该代码的Debug版本如何工作的话, 他/她也不会注意到这类麻烦. 该错误发生在项目的发布版中. 应该被清除的数据被留在了内存中. 原因是编译器有权在优化阶段删除 memset() 函数的调用, 这就是它做的事. 如果你想知道为什么会发生,请阅读该文章"".



开发者们不应该低估复制粘贴的错误, 而认为他也是一种普通的打印错误. 它们数量巨大. 程序员花费很多时间来调试它们.

当然,打印错误和复制粘贴很相似, 但也有不同, 造成我们在本文中將它们放到了不同的分组. 打印错误常常由于使用错误的变量取代所需的变量. 而在 copy-paste 的情形中, 开发者们是忘记编辑复制粘贴的行了.

例 1. Fennec Media 项目. 错误处理数组元素

01void* tag_write_setframe(char *tmem,
02  const char *tid, const string dstr)
03{
04  ...
05  if(lset)
06  {
07    fhead[11] = '\0';
08    fhead[12] = '\0';
09    fhead[13] = '\0';
10    fhead[13] = '\0';
11  }
12  ...
13}

The error was found through the diagnostic: 代码包含了一系列相似的块. 在 716, 717, 718, 719 行中查检元素. id3 editor.c 716

四个相似的行通过复制粘贴的方法必须出现在代码中. 当开发者开始编程下标的时候,他/她犯了个错误导致 0 被写入 'fhead[13]' 两次而没被写到 'fhead[14]'中.

例 2. 项目. 操作数组元素出错

01static int rr_cmp(uchar *a,uchar *b)
02{
03  if (a[0] != b[0])
04    return (int) a[0] - (int) b[0];
05  if (a[1] != b[1])
06    return (int) a[1] - (int) b[1];
07  if (a[2] != b[2])
08    return (int) a[2] - (int) b[2];
09  if (a[3] != b[3])
10    return (int) a[3] - (int) b[3];
11  if (a[4] != b[4])
12    return (int) a[4] - (int) b[4];
13  if (a[5] != b[5])
14    return (int) a[1] - (int) b[5];
15  if (a[6] != b[6])
16    return (int) a[6] - (int) b[6];
17  return (int) a[7] - (int) b[7];
18}

The error was found through the diagnostic: 代码包含了一系列相似的代码块. 在 680, 682, 684, 689, 691, 693, 695 行检查元素 '1', '2', '3', '4', '1', '6'. sql records.cc 680

咋看之下不那么明显,让我们指出来:

1return (int) a[1] - (int) b[5];

实际上应该是下面的代码:

1return (int) a[5] - (int) b[5];

例 3. 项目. 文件名不正确

01BOOL GetImageHlpVersion(DWORD &dwMS, DWORD &dwLS)
02{
03  return(GetInMemoryFileVersion(("DBGHELP.DLL"),
04                                dwMS,
05                                dwLS)) ;
06}
07 
08BOOL GetDbgHelpVersion(DWORD &dwMS, DWORD &dwLS)
09{
10  return(GetInMemoryFileVersion(("DBGHELP.DLL"),
11                                dwMS,
12                                dwLS)) ;
13}

The error was found through the diagnostic: 很奇怪 'GetDbgHelpVersion' 函数与 'GetImageHlpVersion' (SymbolEngine.h) line 98 函数完全相同. symbolengine.h 105

'GetImageHlpVersion' 函数肯定是通过复制粘贴 'GetInMemoryFileVersion' 函数出现的. 错误是: 程序员在被复制粘贴函数中忘记修改文件名了. 下面是正确代码:

1BOOL GetImageHlpVersion(DWORD &dwMS, DWORD &dwLS)
2{
3  return(GetInMemoryFileVersion(("IMAGEHLP.DLL"),
4                                dwMS,
5                                dwLS)) ;
6}

例 4. 项目. 函数体相同

01MapTy PerPtrTopDown;
02MapTy PerPtrBottomUp;
03 
04void clearBottomUpPointers() {
05  PerPtrTopDown.clear();
06}
07 
08void clearTopDownPointers() {
09  PerPtrTopDown.clear();
10}

The error was found through the diagnostic: 很奇怪, 'clearTopDownPointers' 函数体和 'clearBottomUpPointers'(ObjCARC.cpp, line 1318) 函数体完全一样. LLVMScalarOpts objcarc.cpp

clearBottomUpPointers 函数看起来不正确;该函数应写成下面这样:

1void clearBottomUpPointers() {
2  PerPtrBottomUp.clear();
3}

例 5. 项目. 不成功的交换.

1bool qt_testCollision(...)
2{
3  ...
4  t=x1; x1=x2; x2=t;
5  t=y1; x1=y2; y2=t;
6  ...
7}

The error was found through the diagnostic: 'x1' 变量被成功赋值了两次.可能这是个错误. 检查行号: 2218, 2219. Qt3Support q3canvas.cpp 2219

第一行是绝对正确的, 并交换了 x1 和 x2 变量的值. 在第二行, x1 和 x2 必须被交换. 该行可以能是前一行的拷贝.所有的 'x' 字母需要被替换为 'y'.不幸的是,程序员忘记做了:"... x1=y2; ...".

正确代码:

1t=x1; x1=x2; x2=t;
2t=y1; y1=y2; y2=t;

例 6. . 子表达式相同.

1inline_ bool Contains(const LSS& lss)
2{
3  return Contains(Sphere(lss.mP0, lss.mRadius)) &&
4         Contains(Sphere(lss.mP0, lss.mRadius));
5}

The error was found through the diagnostic:在 '&&' 操作符的两边有个相同的子表达式. plgcsopcode icelss.h 69

错误是:'lss.mP0.'值被使用了两次.表达式的第一部份必须是 'lss.mP1'.

例 7. 项止. 不正确的风格设置.

01void KeyWordsStyleDialog::updateDlg()
02{
03  ...
04  Style & w1Style =
05    _pUserLang->_styleArray.getStyler(STYLE_WORD1_INDEX);
06  styleUpdate(w1Style, _pFgColour[0], _pBgColour[0],
07    IDC_KEYWORD1_FONT_COMBO, IDC_KEYWORD1_FONTSIZE_COMBO,
08    IDC_KEYWORD1_BOLD_CHECK, IDC_KEYWORD1_ITALIC_CHECK,
09    IDC_KEYWORD1_UNDERLINE_CHECK);
10 
11  Style & w2Style =
12    _pUserLang->_styleArray.getStyler(STYLE_WORD2_INDEX);
13  styleUpdate(w2Style, _pFgColour[1], _pBgColour[1],
14    IDC_KEYWORD2_FONT_COMBO, IDC_KEYWORD2_FONTSIZE_COMBO,
15    IDC_KEYWORD2_BOLD_CHECK, IDC_KEYWORD2_ITALIC_CHECK,
16    IDC_KEYWORD2_UNDERLINE_CHECK);
17 
18  Style & w3Style =
19    _pUserLang->_styleArray.getStyler(STYLE_WORD3_INDEX);
20  styleUpdate(w3Style, _pFgColour[2], _pBgColour[2],
21    IDC_KEYWORD3_FONT_COMBO, IDC_KEYWORD3_FONTSIZE_COMBO,
22    IDC_KEYWORD3_BOLD_CHECK, IDC_KEYWORD3_BOLD_CHECK,
23    IDC_KEYWORD3_UNDERLINE_CHECK);
24 
25  Style & w4Style =
26    _pUserLang->_styleArray.getStyler(STYLE_WORD4_INDEX);
27  styleUpdate(w4Style, _pFgColour[3], _pBgColour[3],
28    IDC_KEYWORD4_FONT_COMBO, IDC_KEYWORD4_FONTSIZE_COMBO,
29    IDC_KEYWORD4_BOLD_CHECK, IDC_KEYWORD4_ITALIC_CHECK,
30    IDC_KEYWORD4_UNDERLINE_CHECK);
31  ...
32}

The error was found through the diagnostic: 代码包含了一系列相似的代码块. 检查元素 '7', '7', '6', '7' 在 576, 580, 584, 588 行.

肉眼几乎很难发现错误, 让我们缩减文本挑选感兴趣的片段:

01styleUpdate(...
02  IDC_KEYWORD1_BOLD_CHECK, IDC_KEYWORD1_ITALIC_CHECK,
03  ...);
04styleUpdate(...
05  IDC_KEYWORD2_BOLD_CHECK, IDC_KEYWORD2_ITALIC_CHECK,
06  ...);
07styleUpdate(...
08  IDC_KEYWORD3_BOLD_CHECK, IDC_KEYWORD3_BOLD_CHECK, <<--
09  ...);
10styleUpdate(...
11  IDC_KEYWORD4_BOLD_CHECK, IDC_KEYWORD4_ITALIC_CHECK,
12  ...);

出错了, IDC_KEYWORD3_BOLD_CHECK被用来取代IDC_KEYWORD3_ITALIC_CHECK了.

例 8. 对象. 选择了错误的对象类型

01void CardButton::DrawRect(HDC hdc, RECT *rect, bool fNormal)
02{
03  ...
04  HPEN hhi = CreatePen(0, 0, MAKE_PALETTERGB(crHighlight));
05  HPEN hsh = CreatePen(0, 0, MAKE_PALETTERGB(crShadow));
06  ...
07  if(fNormal)
08    hOld = SelectObject(hdc, hhi);
09  else
10    hOld = SelectObject(hdc, hhi);
11  ...
12}

The error was found through the diagnostic:  'then' 被当做 'else' 了. cardlib cardbutton.cpp 83

'hsh' 对象未被使用,而 'hhi'被使用了两次.以下是正确代码:

1if(fNormal)
2  hOld = SelectObject(hdc, hhi);
3else
4  hOld = SelectObject(hdc, hsh);

例 9. 项目. 不正确的检查.

1Status VC1VideoDecoder::ResizeBuffer()
2{
3  ...
4  if(m_pContext && m_pContext->m_seqLayerHeader &&
5     m_pContext->m_seqLayerHeader->heightMB &&
6     m_pContext->m_seqLayerHeader->heightMB) 
7  ...
8}

The error was found through the diagnostic: 在 '&&' 操作的两边有两个相同的子表达式 'm_pContext->m_seqLayerHeader->heightMB'. operator. vc1_dec umc_vc1_video_decoder.cpp 1347

正确代码:

1if(m_pContext && m_pContext->m_seqLayerHeader &&
2  m_pContext->m_seqLayerHeader->heightMB &&
3  m_pContext->m_seqLayerHeader->widthMB)

例 10. 项目. 变量名错误.

1BOOL APIENTRY
2GreStretchBltMask(...)
3{
4  ...
5  MaskPoint.x += DCMask->ptlDCOrig.x;
6  MaskPoint.y += DCMask->ptlDCOrig.x;
7  ...
8}

The error was found through the diagnostic: 认真审查 'x' 元素的使用. win32k bitblt.c 670

这是一个非常好的例子, 你可以看出一行被复制和粘贴了. 之后, 程序员修复了第一个名称 'x' 但忘记了改正第二个了. 下面是正确代码:

1MaskPoint.x += DCMask->ptlDCOrig.x;
2MaskPoint.y += DCMask->ptlDCOrig.y;

Late check of null pointers

C/C++程序员不得不在任何时候大量的检查指针, 确保他们不等于 0 . 既然该类检查数量巨大的, 犯错的机会也就很大. 常常发生在一个指针先被使用而后才和 NULL 比较.这类错误很少显露出来. 通常程序在标准模式下工作良好,而仅在非标准模下失败. 不同于正常模式下的空指针处理, 将发生且一个异常会被抛出.

例 1. 项目. 逾期检查.

01void Item_Paint(itemDef_t *item) {
02  vec4_t red;
03  menuDef_t *parent = (menuDef_t*)item->parent;
04  red[0] = red[3] = 1;
05  red[1] = red[2] = 0;
06  if (item == NULL) {
07    return;
08  }
09  ...
10}

The error has been found with rule : 'item' 在未验证空指针前被使用. Check lines: 3865, 3869. cgame ui_shared.c 3865

'item' 指针先被使用而后才与 NULL 比较.

例 2. LAME Ain't an MP3 Encoder 项目. 逾期检查.

1static int
2check_vbr_header(PMPSTR mp, int bytes)
3{
4  ...
5  buf  = buf->next;
6  pos = buf->pos;
7  if(!buf) return -1; /* fatal error */
8  ...
9}

The error has been found with rule : 'buf' 指针在空指针验证前被使用. Check lines: 226, 227. mpglib interface.c 226

'buf' 等于 NULL,一个异常將被抛出而不是返回一个错误码. 如果异常未被捕获,该程序将崩溃.

例 3. daoParanoia library 项目. 逾期检查.

1static long i_stage2_each(root_block *root,
2  v_fragment *v, void(*callback)(long,int))
3{
4  cdrom_paranoia *p=v->p;
5  long dynoverlap=p->dynoverlap/2*2;
6  if (!v || !v->one) return(0);
7  ...
8}

The error has been found with rule : 'v' 指针在空指针验证前被使用.Check lines: 532, 535. daoParanoia paranoia.c 532

这里的情况和前例一样.

例 4. TrinityCore 项目. 逾期检查.

01bool OnCheck(Player* player, Unit* /*target*/)
02{
03  bool checkArea =
04    player->GetAreaId() == AREA_ARGENT_TOURNAMENT_FIELDS ||
05    player->GetAreaId() == AREA_RING_OF_ASPIRANTS ||
06    player->GetAreaId() == AREA_RING_OF_ARGENT_VALIANTS ||
07    player->GetAreaId() == AREA_RING_OF_ALLIANCE_VALIANTS ||
08    player->GetAreaId() == AREA_RING_OF_HORDE_VALIANTS ||
09    player->GetAreaId() == AREA_RING_OF_CHAMPIONS;
10 
11  return player && checkArea && player->duel &&
12         player->duel->isMounted;
13}
14The error has been found with rule : The 'player' pointer was utilized before it was verified against nullptr. Check lines: 310, 312. scripts achievement_scripts.cpp 310

你可以从 "player && ..."条件中看出, 'player' 指针可能等于 0. 然后像前面几个例子一样的检查, 逾期了.

我们还可以引用很多这类例子, 但它们都很相似. 如果你看到了一些这类例子, 确保你是见过他们的.

例 1. Image Processing SDK 项目. 八进制数.

01inline
02void elxLuminocity(const PixelRGBus& iPixel,
03  LuminanceCell< PixelRGBus >& oCell)
04{
05  oCell._luminance = uint16(0.2220f*iPixel._red +
06    0.7067f*iPixel._blue + 0.0713f*iPixel._green);
07  oCell._pixel = iPixel;
08}
09 
10inline
11void elxLuminocity(const PixelRGBi& iPixel,
12  LuminanceCell< PixelRGBi >& oCell)
13{
14  oCell._luminance = 2220*iPixel._red +
15    7067*iPixel._blue + 0713*iPixel._green;
16  oCell._pixel = iPixel;
17}

The error was found through the diagnostic: 被告知, 一个常量值被八进制的形式代表了. Oct: 0713, Dec: 459. IFF plugins pixelservices.inl 146

如果你检查第二个函数, 你会看到程序员的意图是使用数字 713 而不是 0713. 0713 是定义八进制数字的. 如果你很少使用八进制常量, 你会很容易忘记它.

Example 2. 项目. One variable for two loops.

例 2. 项目. 一个变量双循环.

01JERRCODE CJPEGDecoder::DecodeScanBaselineNI(void)
02{
03  ...
04  for(c = 0; c < m_scan_ncomps; c++)
05  {
06    block = m_block_buffer + (DCTSIZE2*m_nblock*(j+(i*m_numxMCU)));
07 
08    // skip any relevant components
09    for(c = 0; c < m_ccomp[m_curr_comp_no].m_comp_no; c++)
10    {
11      block += (DCTSIZE2*m_ccomp[c].m_nblocks);
12    }
13  ...
14}

The error was found through the diagnostic: 'c' 变量被当前循环和外层循环使用了. jpegcodec jpegdec.cpp 4652

内层循环和外层循环使用同名的变量. 因此,该代码仅处理部分数据或导致一个死循环.

例 3. 项目. 缺少 return.

1static ID_INLINE int BigLong(int l)
2{ LongSwap(l); }

The error has been found with rule : Non-void function should return a value. botlib q_shared.h 155

该代码使用C编写. 意味着编译器不需要显示的返回值. 但这里相当必要. 然而,该代码能工作完全靠运气.一切都依赖于 EAX 寄存器包含了什么.但这仅仅是运气,无他. 函数体应该被写成这样:{ return LongSwap(l) };

例4. . 冗余条件.

1int Notepad_plus::getHtmlXmlEncoding(....) const
2{
3  ...
4  if (langT != L_XML && langT != L_HTML && langT == L_PHP)
5    return -1;
6  ...
7}

The error has been found with rule : Consider inspecting this expression. The expression is excessive or contains a misprint. Notepad++ notepad_plus.cpp 853

能这仅仅是个打印错误, 但它也可能在运算分解中出现. 然而,这里很明显. 条件可以更简单: if (langT == L_PHP). 代码必须看起来像这样:

1if (langT != L_XML && langT != L_HTML && langT != L_PHP)
阅读(2896) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~