如何在C和C ++中放纵自己。 Haiku OS食谱集

带有Haiku操作系统代码的PVS-Studio静态分析仪会议的历史可以追溯到2015年。 对于这两个项目的团队来说,这都是一次有趣的实验,并且是有益的经验。 为什么要进行实验? 那时没有针对Linux的分析器,也不会再有一年半的时间。 但是我们团队的发烧友的工作得到了回报:我们与Haiku开发人员会面,并提高了代码质量,为数据库补充了罕见的程序员错误并完善了分析器。 现在,检查Haiku的代码是否有错误非常容易。

图片3


引言


我们历史上的英雄是开源的Haiku操作系统以及用于C,C ++,C#和Java的PVS-Studio静态分析器。 4.5年前,当我们开始分析项目时,我们只需要使用编译的分析器可执行文件。 解析编译参数,启动预处理器,并行化分析等的整个基础结构。 取自C#中的Compiler Monitoring UI实用程序,该实用程序已部分移植到Mono平台,以便在Linux上运行。 Haiku项目本身是使用交叉编译器在各种操作系统(Windows除外)下构建的。 我想再次指出Haiku程序集文档的便利性和完整性,并感谢Haiku开发人员在构建项目中的帮助。

现在,分析变得容易得多。 用于构建和分析项目的所有命令的列表如下所示:

cd /opt git clone https://review.haiku-os.org/buildtools git clone https://review.haiku-os.org/haiku cd ./haiku mkdir generated.x86_64; cd generated.x86_64 ../configure --distro-compatibility official -j12 \ --build-cross-tools x86_64 ../../buildtools cd ../../buildtools/jam make all cd /opt/haiku/generated.x86_64 pvs-studio-analyzer trace -- /opt/buildtools/jam/bin.linuxx86/jam \ -q -j1 @nightly-anyboot pvs-studio-analyzer analyze -l /mnt/svn/PVS-Studio.lic -r /opt/haiku \ -C x86_64-unknown-haiku-gcc -o /opt/haiku/haiku.log -j12 

顺便说一下,项目分析是在Docker容器中进行的。 最近,我们准备了有关该主题的新文档: 在Docker中运行PVS-Studio 。 这可以大大简化某些公司使用静态项目分析技术的过程。

未初始化的变量


V614使用了未初始化的变量“ rval”。 1727年

 int auto_fetch(int argc, char *argv[]) { volatile int argpos; int rval; // <= argpos = 0; if (sigsetjmp(toplevel, 1)) { if (connected) disconnect(0, NULL); if (rval > 0) // <= rval = argpos + 1; return (rval); } .... } 

rval变量在声明时未初始化,因此将其与null值进行比较将导致不确定的结果。 如果情况失败,则rval变量的未定义值可能会成为auto_fetch函数的返回值。

V614使用了未初始化的指针“ res”。 命令.c 2873

 struct addrinfo { int ai_flags; int ai_family; int ai_socktype; int ai_protocol; socklen_t ai_addrlen; char *ai_canonname; struct sockaddr *ai_addr; struct addrinfo *ai_next; }; static int sourceroute(struct addrinfo *ai, char *arg, char **cpp, int *lenp, int *protop, int *optp) { static char buf[1024 + ALIGNBYTES]; char *cp, *cp2, *lsrp, *ep; struct sockaddr_in *_sin; #ifdef INET6 struct sockaddr_in6 *sin6; struct ip6_rthdr *rth; #endif struct addrinfo hints, *res; // <= int error; char c; if (cpp == NULL || lenp == NULL) return -1; if (*cpp != NULL) { switch (res->ai_family) { // <= case AF_INET: if (*lenp < 7) return -1; break; .... } } .... } 

使用未初始化变量的类似情况,仅此处是未初始化指针res

V506指向本地变量“归一化”的指针存储在该变量范围之外。 这样的指针将变为无效。 TextView.cpp 5596

 void BTextView::_ApplyStyleRange(...., const BFont* font, ....) { if (font != NULL) { BFont normalized = *font; _NormalizeFont(&normalized); font = &normalized; } .... fStyles->SetStyleRange(fromOffset, toOffset, fText->Length(), mode, font, color); } 

程序员可能需要通过中间变量对对象进行规范化。 但是现在,在字体指针中保存了指向规范化临时对象的指针,该指针在离开创建该临时对象的作用域之后将被销毁。

V603对象已创建,但未被使用。 如果要调用构造函数,则应使用'this-> BUnicodeChar :: BUnicodeChar(....)'。 UnicodeChar.cpp 27

 int8 BUnicodeChar::Type(uint32 c) { BUnicodeChar(); return u_charType(c); } 

在C ++程序员中,一个非常普遍的错误是使用构造函数调用来初始化/归零类字段。 在这种情况下,不会修改类的字段,但是会创建此类的一个新的未命名对象,该对象将立即销毁。 不幸的是,有很多这样的地方:

  • V603对象已创建,但未被使用。 如果要调用构造函数,则应使用'this-> BUnicodeChar :: BUnicodeChar(....)'。 UnicodeChar.cpp 37
  • V603对象已创建,但未被使用。 如果要调用构造函数,则应使用'this-> BUnicodeChar :: BUnicodeChar(....)'。 UnicodeChar.cpp 49
  • V603对象已创建,但未被使用。 如果要调用构造函数,则应使用'this-> BUnicodeChar :: BUnicodeChar(....)'。 UnicodeChar.cpp 58
  • V603对象已创建,但未被使用。 如果要调用构造函数,则应使用'this-> BUnicodeChar :: BUnicodeChar(....)'。 UnicodeChar.cpp 67
  • V603对象已创建,但未被使用。 如果要调用构造函数,则应使用'this-> BUnicodeChar :: BUnicodeChar(....)'。 UnicodeChar.cpp 77
  • V603对象已创建,但未被使用。 如果要调用构造函数,则应使用'this-> BUnicodeChar :: BUnicodeChar(....)'。 UnicodeChar.cpp 89
  • V603对象已创建,但未被使用。 如果要调用构造函数,则应使用'this-> BUnicodeChar :: BUnicodeChar(....)'。 UnicodeChar.cpp 103
  • V603对象已创建,但未被使用。 如果要调用构造函数,则应使用'this-> BUnicodeChar :: BUnicodeChar(....)'。 UnicodeChar.cpp 115
  • V603对象已创建,但未被使用。 如果要调用构造函数,则应使用'this-> BUnicodeChar :: BUnicodeChar(....)'。 UnicodeChar.cpp 126
  • V603对象已创建,但未被使用。 如果要调用构造函数,则应使用'this-> BUnicodeChar :: BUnicodeChar(....)'。 UnicodeChar.cpp 142
  • V603对象已创建,但未被使用。 如果要调用构造函数,则应使用'this-> BUnicodeChar :: BUnicodeChar(....)'。 UnicodeChar.cpp 152
  • V603对象已创建,但未被使用。 如果要调用构造函数,则应使用'this-> BUnicodeChar :: BUnicodeChar(....)'。 UnicodeChar.cpp 163
  • V603对象已创建,但未被使用。 如果要调用构造函数,则应使用'this-> BUnicodeChar :: BUnicodeChar(....)'。 UnicodeChar.cpp 186
  • V603对象已创建,但未被使用。 如果要调用构造函数,则应使用'this-> BUnicodeChar :: BUnicodeChar(....)'。 UnicodeChar.cpp 196
  • V603对象已创建,但未被使用。 如果要调用构造函数,则应使用'this-> BUnicodeChar :: BUnicodeChar(....)'。 UnicodeChar.cpp 206
  • V603对象已创建,但未被使用。 如果要调用构造函数,则应使用'this-> BUnicodeChar :: BUnicodeChar(....)'。 UnicodeChar.cpp 214
  • V603对象已创建,但未被使用。 如果要调用构造函数,则应使用'this-> BUnicodeChar :: BUnicodeChar(....)'。 UnicodeChar.cpp 222
  • V603对象已创建,但未被使用。 如果要调用构造函数,则应使用'this-> BUnicodeChar :: BUnicodeChar(....)'。 UnicodeChar.cpp 230

V670未初始化的类成员'fPatternHandler'用于初始化'fInternal'成员。 请记住,成员是按照类内声明的顺序进行初始化的。 Painter.cpp 184

 Painter::Painter() : fInternal(fPatternHandler), .... fPatternHandler(), .... { .... }; class Painter { .... private: mutable PainterAggInterface fInternal; // line 336 bool fSubpixelPrecise : 1; bool fValidClipping : 1; bool fDrawingText : 1; bool fAttached : 1; bool fIdentityTransform : 1; Transformable fTransform; float fPenSize; const BRegion* fClippingRegion; drawing_mode fDrawingMode; source_alpha fAlphaSrcMode; alpha_function fAlphaFncMode; cap_mode fLineCapMode; join_mode fLineJoinMode; float fMiterLimit; PatternHandler fPatternHandler; // line 355 mutable AGGTextRenderer fTextRenderer; }; 

错误初始化的另一个示例。 按照在类本身中声明的顺序初始化类字段。 在此示例中,将首先使用未初始化的值fPatternHandler初始化fInternal字段。

可疑的#define


V523'then '语句等效于'else'语句。 subr_gtaskqueue.c 191

 #define TQ_LOCK(tq) \ do { \ if ((tq)->tq_spin) \ mtx_lock_spin(&(tq)->tq_mutex); \ else \ mtx_lock(&(tq)->tq_mutex); \ } while (0) #define TQ_ASSERT_LOCKED(tq) mtx_assert(&(tq)->tq_mutex, MA_OWNED) #define TQ_UNLOCK(tq) \ do { \ if ((tq)->tq_spin) \ mtx_unlock_spin(&(tq)->tq_mutex); \ else \ mtx_unlock(&(tq)->tq_mutex); \ } while (0) void grouptask_block(struct grouptask *grouptask) { .... TQ_LOCK(queue); gtask->ta_flags |= TASK_NOENQUEUE; gtaskqueue_drain_locked(queue, gtask); TQ_UNLOCK(queue); } 

除非您查看预处理器的结果,否则代码片段看起来并不可疑:

 void grouptask_block(struct grouptask *grouptask) { .... do { if ((queue)->tq_spin) mtx_lock(&(queue)->tq_mutex); else mtx_lock(&(queue)->tq_mutex); } while (0); gtask->ta_flags |= 0x4; gtaskqueue_drain_locked(queue, gtask); do { if ((queue)->tq_spin) mtx_unlock(&(queue)->tq_mutex); else mtx_unlock(&(queue)->tq_mutex); } while (0); } 

分析器是正确的-ifelse分支相同。 但是mtx_lock_spinmtx_unlock_spin函数去哪里了 ? 宏TQ_LOCKTQ_UNLOCKgrouptask_block函数在同一文件声明的,并且几乎并排声明,但是在某处存在替换。

对文件内容的搜索仅找到具有以下内容的mutex.h

 /* on FreeBSD these are different functions */ #define mtx_lock_spin(x) mtx_lock(x) #define mtx_unlock_spin(x) mtx_unlock(x) 

对于项目开发人员而言,这种替换是否正确是值得检查的。 我在Linux上检查了该项目,这种替换对我来说似乎很可疑。

自由功能的错误


V575空指针传递到“免费”功能。 检查第一个参数。 setmime.cpp 727

 void MimeType::_PurgeProperties() { fShort.Truncate(0); fLong.Truncate(0); fPrefApp.Truncate(0); fPrefAppSig.Truncate(0); fSniffRule.Truncate(0); delete fSmallIcon; fSmallIcon = NULL; delete fBigIcon; fBigIcon = NULL; fVectorIcon = NULL; // <= free(fVectorIcon); // <= fExtensions.clear(); fAttributes.clear(); } 

您可以将空指针传递给free函数,但是这样的输入显然是可疑的。 因此,分析器发现了混乱的代码行。 首先,您必须使用指针fVectorIcon释放内存,然后才将其设置为NULL

V575空指针传递到“免费”功能。 检查第一个参数。 driver_settings.cpp 461

 static settings_handle * load_driver_settings_from_file(int file, const char *driverName) { .... handle = new_settings(text, driverName); if (handle != NULL) { // everything went fine! return handle; } free(handle); // <= .... } 

将空指针显式传递给free函数的另一个示例。 可以删除此行,因为 成功接收到指针后,函数将退出。

V575空指针传递到“免费”功能。 检查第一个参数。 PackageFileHeapWriter.cpp 166

 void* _GetBuffer() { .... void* buffer = malloc(fBufferSize); if (buffer == NULL && !fBuffers.AddItem(buffer)) { free(buffer); throw std::bad_alloc(); } return buffer; } 

这是一个错误。 必须使用||运算符代替&&运算符。 仅在这种情况下,如果不可能使用malloc函数分配内存,则将引发std :: bad_alloc()异常。

删除运算符错误


V611使用“ new T []”运算符分配了内存,但使用“ delete”运算符释放了内存。 考虑检查此代码。 最好使用'delete [] fMsg;'。 错误cpp 65

 class Err { public: .... private: char *fMsg; ssize_t fPos; }; void Err::Unset() { delete fMsg; // <= fMsg = __null; fPos = -1; } void Err::SetMsg(const char *msg) { if (fMsg) { delete fMsg; // <= fMsg = __null; } if (msg) { fMsg = new(std::nothrow) char[strlen(msg)+1]; // <= if (fMsg) strcpy(fMsg, msg); } } 

fMsg指针用于分配用于存储字符数组的内存,而delete运算符用于释放内存,而不是delete []

V611使用“ new”运算符分配了内存,但使用“ free”功能释放了内存。 考虑检查“ wrapperPool”变量后面的操作逻辑。 vm_page.cpp 3080

 status_t vm_page_write_modified_page_range(....) { .... PageWriteWrapper* wrapperPool = new(malloc_flags(allocationFlags)) PageWriteWrapper[maxPages + 1]; PageWriteWrapper** wrappers = new(malloc_flags(allocationFlags)) PageWriteWrapper*[maxPages]; if (wrapperPool == NULL || wrappers == NULL) { free(wrapperPool); // <= free(wrappers); // <= wrapperPool = stackWrappersPool; wrappers = stackWrappers; maxPages = 1; } .... } 

这里的malloc_flags是创建malloc的函数。 然后, placement-new在此处构造对象。 并且由于PageWriteWrapper实现如下:

 class PageWriteWrapper { public: PageWriteWrapper(); ~PageWriteWrapper(); void SetTo(vm_page* page); bool Done(status_t result); private: vm_page* fPage; struct VMCache* fCache; bool fIsActive; }; PageWriteWrapper::PageWriteWrapper() : fIsActive(false) { } PageWriteWrapper::~PageWriteWrapper() { if (fIsActive) panic("page write wrapper going out of scope but isn't completed"); } 

那么由于使用了free函数来释放内存,此类的对象析构函数将不会被调用。

V611使用“ new T []”运算符分配了内存,但使用“ delete”运算符释放了内存。 考虑检查此代码。 最好使用'delete [] fOutBuffer;'。 检查行:26,45。PCL6Rasterizer.h 26

 class PCL6Rasterizer : public Rasterizer { public: .... ~PCL6Rasterizer() { delete fOutBuffer; fOutBuffer = NULL; } .... virtual void InitializeBuffer() { fOutBuffer = new uchar[fOutBufferSize]; } private: uchar* fOutBuffer; int fOutBufferSize; }; 

使用delete运算符而不是delete []是一个非常常见的错误。 编写课程时犯错误的最简单方法是 析构函数代码通常位于远离内存分配位置的位置。 在这里,程序员错误地通过指针fOutBuffer释放了析构函数中的内存。

V772为空指针调用“删除”运算符将导致未定义的行为。 哈希表.cpp 207

 void Hashtable::MakeEmpty(int8 keyMode,int8 valueMode) { .... for (entry = fTable[index]; entry; entry = next) { switch (keyMode) { case HASH_EMPTY_DELETE: // TODO: destructors are not called! delete (void*)entry->key; break; case HASH_EMPTY_FREE: free((void*)entry->key); break; } switch (valueMode) { case HASH_EMPTY_DELETE: // TODO: destructors are not called! delete entry->value; break; case HASH_EMPTY_FREE: free(entry->value); break; } next = entry->next; delete entry; } .... } 

除了在delete / delete []free之间选择错误之外,您还可以通过另一种方式向程序添加未定义的行为-尝试通过指向未定义类型(void *)的指针清除内存。

没有返回值的功能


V591非无效函数应返回一个值。 h 228

 BReference& operator=(const BReference<const Type>& other) { fReference = other.fReference; } 

重写的赋值运算符没有足够的返回值。 在这种情况下,操作员将返回一个随机值,这可能会导致奇怪的错误。

此类的其他代码段中的类似问题:

  • V591非无效函数应返回一个值。 h 233
  • V591非无效函数应返回一个值。 h 239

V591非无效函数应返回一个值。 main.c 1010

 void errx(int, const char *, ...) ; char * getoptionvalue(const char *name) { struct option *c; if (name == NULL) errx(1, "getoptionvalue() invoked with NULL name"); c = getoption(name); if (c != NULL) return (c->value); errx(1, "getoptionvalue() invoked with unknown option '%s'", name); /* NOTREACHED */ } 

这里的NOTREACHED用户评论没有任何意义。 要为此类情况正确编写代码,必须标记诸如noreturn之类的功能。 没有返回属性:标准和特定于编译器。 首先,编译器将这些属性考虑在内,以通过警告来正确生成代码或通知某些类型的错误。 各种静态分析工具还考虑了属性,以提高分析质量。

异常处理


V596对象已创建,但未使用。 'throw'关键字可能丢失:throw ParseException(FOO); Response.cpp 659

 size_t Response::ExtractNumber(BDataIO& stream) { BString string = ExtractString(stream); const char* end; size_t number = strtoul(string.String(), (char**)&end, 10); if (end == NULL || end[0] != '\0') ParseException("Invalid number!"); return number; } 

在这里, throw关键字被意外遗忘了。 因此,不会引发ParseException ,并且此类的对象在离开范围时将被简单地销毁。 之后,该功能将继续工作,就好像什么都没发生一样,好像输入了正确的数字一样。

V1022指针抛出异常。 考虑改为按值扔掉它。 gensyscallinfos.cpp 316

 int main(int argc, char** argv) { try { return Main().Run(argc, argv); } catch (Exception& exception) { // <= fprintf(stderr, "%s\n", exception.what()); return 1; } } int Run(int argc, char** argv) { .... _ParseSyscalls(argv[1]); .... } void _ParseSyscalls(const char* filename) { ifstream file(filename, ifstream::in); if (!file.is_open()) throw new IOException(string("Failed to open '") + filename + "'."); // <= .... } 

分析器检测到指针抛出了IOException 。 抛出指针将导致不捕获异常,因此最终通过引用捕获了异常。 另外,使用指针会强制拦截器调用delete运算符以破坏创建的对象,这也没有完成。

代码的另外两个问题区域:

  • V1022指针抛出异常。 考虑改为按值扔掉它。 gensyscallinfos.cpp 347
  • V1022指针抛出异常。 考虑改为按值扔掉它。 gensyscallinfos.cpp 413

形式安全


V597编译器可能会删除“ memset”函数调用,该函数调用用于刷新“ f_key”对象。 memset_s()函数应用于擦除私有数据。 dst_api.c 1018

 #ifndef SAFE_FREE #define SAFE_FREE(a) \ do{if(a != NULL){memset(a,0, sizeof(*a)); free(a); a=NULL;}} while (0) .... #endif DST_KEY * dst_free_key(DST_KEY *f_key) { if (f_key == NULL) return (f_key); if (f_key->dk_func && f_key->dk_func->destroy) f_key->dk_KEY_struct = f_key->dk_func->destroy(f_key->dk_KEY_struct); else { EREPORT(("dst_free_key(): Unknown key alg %d\n", f_key->dk_alg)); } if (f_key->dk_KEY_struct) { free(f_key->dk_KEY_struct); f_key->dk_KEY_struct = NULL; } if (f_key->dk_key_name) SAFE_FREE(f_key->dk_key_name); SAFE_FREE(f_key); return (NULL); } 

分析仪检测到旨在安全清除私有数据的可疑代码。 不幸的是, SAFE_FREE宏扩展为memset免费调用和NULL分配,并不能使代码更安全,因为 在O2优化期间,编译器将所有这些都删除。

顺便说一句,这与CWE-14完全不同 :清除代码以清除缓冲区的编译器。

缓冲区实际未清除的完整列表:

  • V597编译器可以删除“内存集”函数调用,该函数调用用于刷新“ encoded_block”缓冲区。 memset_s()函数应用于擦除私有数据。 dst_api.c 446
  • V597编译器可以删除“内存集”函数调用,该函数调用用于刷新“ key_st”对象。 memset_s()函数应用于擦除私有数据。 dst_api.c 685
  • V597编译器可以删除“内存集”函数调用,该函数调用用于刷新“ in_buff”缓冲区。 memset_s()函数应用于擦除私有数据。 dst_api.c 916
  • V597编译器可以删除“内存集”函数调用,该函数调用用于刷新“ ce”对象。 memset_s()函数应用于擦除私有数据。 fs_cache.c 1078

无符号比较


V547表达式“剩余<0”始终为false。 无符号类型值永远不会小于0。DwarfFile.cpp 1947

 status_t DwarfFile::_UnwindCallFrame(....) { .... uint64 remaining = lengthOffset + length - dataReader.Offset(); if (remaining < 0) return B_BAD_DATA; .... } 

分析器发现了一个无符号变量与负值的显式比较。 也许您应该将其余变量仅零进行比较,或者实施溢出检查。

V547表达式“ sleep((unsigned)secs)<0”始终为false。 无符号类型值永远不会小于0。misc.cpp 56

 status_t snooze(bigtime_t amount) { if (amount <= 0) return B_OK; int64 secs = amount / 1000000LL; int64 usecs = amount % 1000000LL; if (secs > 0) { if (sleep((unsigned)secs) < 0) // <= return errno; } if (usecs > 0) { if (usleep((useconds_t)usecs) < 0) return errno; } return B_OK; } 

要了解错误是什么,让我们来看一下sleepusleep函数的签名:

 extern unsigned int sleep (unsigned int __seconds); extern int usleep (__useconds_t __useconds); 

如我们所见, sleep函数返回一个无符号类型的值,并且在代码中的使用不正确。

危险的指针


V774释放内存后使用了“设备”指针。 xhci.cpp 1572

 void XHCI::FreeDevice(Device *device) { uint8 slot = fPortSlots[device->HubPort()]; TRACE("FreeDevice() port %d slot %d\n", device->HubPort(), slot); // Delete the device first, so it cleans up its pipes and tells us // what we need to destroy before we tear down our internal state. delete device; DisableSlot(slot); fDcba->baseAddress[slot] = 0; fPortSlots[device->HubPort()] = 0; // <= delete_area(fDevices[slot].trb_area); delete_area(fDevices[slot].input_ctx_area); delete_area(fDevices[slot].device_ctx_area); memset(&fDevices[slot], 0, sizeof(xhci_device)); fDevices[slot].state = XHCI_STATE_DISABLED; } 

设备对象由删除运算符清除。 这是名为FreeDevice的函数的逻辑操作。 但是,由于某种原因,要释放代码中的其他资源,再次呼吁已经删除的对象。

这样的代码非常危险,并且会在其他几个地方发生:

  • V774释放内存后使用了“自我”指针。 第884章
  • V774释放内存后使用了“字符串”指针。 RemoteView.cpp 1269
  • V774释放内存后使用了“ bs”指针。 mkntfs.c 4291
  • V774释放内存后使用了“ bs”指针。 mkntfs.c 4308
  • V774在重新分配内存后使用了“ al”指针。 inode.c 1155

V522可能会取消引用空指针“数据”。 空指针将传递给“ malo_hal_send_helper”函数。 检查第三个论点。 检查行:350、394。if_malohal.c 350

 static int malo_hal_fwload_helper(struct malo_hal *mh, char *helper) { .... /* tell the card we're done and... */ error = malo_hal_send_helper(mh, 0, NULL, 0, MALO_NOWAIT); // <= NULL .... } static int malo_hal_send_helper(struct malo_hal *mh, int bsize, const void *data, size_t dsize, int waitfor) { mh->mh_cmdbuf[0] = htole16(MALO_HOSTCMD_CODE_DNLD); mh->mh_cmdbuf[1] = htole16(bsize); memcpy(&mh->mh_cmdbuf[4], data , dsize); // <= data .... } 

过程间分析显示了一种情况,其中将NULL传递给该函数,然后在memcpy函数中取消引用具有此值的数据指针。

V773在不释放'inputFileFile'指针的情况下退出了该函数。 可能发生内存泄漏。 command_recompress.cpp 119

 int command_recompress(int argc, const char* const* argv) { .... BFile* inputFileFile = new BFile; error = inputFileFile->SetTo(inputPackageFileName, O_RDONLY); if (error != B_OK) { fprintf(stderr, "Error: Failed to open input file \"%s\": %s\n", inputPackageFileName, strerror(error)); return 1; } inputFile = inputFileFile; .... } 

PVS-Studio可以检测到内存泄漏 。 发生某种错误时, 此处不会释放inputFileFile的内存。 有人认为,如果出现错误,您不必费心释放内存-该程序仍将结束。 但这并非总是如此。 正确处理错误并继续工作-这是许多程序的要求。

V595在对nullptr进行验证之前,已使用“ fReply”指针。 检查行:49,52。ReplyBuilder.cpp 49

 RPC::CallbackReply* ReplyBuilder::Reply() { fReply->Stream().InsertUInt(fStatusPosition, _HaikuErrorToNFS4(fStatus)); fReply->Stream().InsertUInt(fOpCountPosition, fOpCount); if (fReply == NULL || fReply->Stream().Error() == B_OK) return fReply; else return NULL; } 

开发人员在检查指针之前多长时间取消引用指针一次。 诊断V595几乎始终是项目中警告数量的领导者。 在这段代码中危险地使用fReply指针。

V595在对nullptr进行验证之前,已使用了'mq'指针。 检查行:782、786。oce_queue.c 782

 static void oce_mq_free(struct oce_mq *mq) { POCE_SOFTC sc = (POCE_SOFTC) mq->parent; struct oce_mbx mbx; struct mbx_destroy_common_mq *fwcmd; if (!mq) return; .... } 

一个类似的例子。 mg指针在被检查为空值之前要取消引用几行。 项目中有很多类似的地方。 在某些地方,使用和检查指针相距很远,因此本文中包含两个示例。 其余的人将能够在完整的分析器报告中看到开发人员。

杂项错误


V645'strncat '函数调用可能导致'输出'缓冲区溢出。 边界不应包含缓冲区的大小,而应包含可以容纳的多个字符。 命名空间转储.cpp 101

 static void dump_acpi_namespace(acpi_ns_device_info *device, char *root, int indenting) { char output[320]; char tabs[255] = ""; .... strlcat(tabs, "|--- ", sizeof(tabs)); .... while (....) { uint32 type = device->acpi->get_object_type(result); snprintf(output, sizeof(output), "%s%s", tabs, result + depth); switch(type) { case ACPI_TYPE_INTEGER: strncat(output, " INTEGER", sizeof(output)); break; case ACPI_TYPE_STRING: strncat(output, " STRING", sizeof(output)); break; .... } .... } .... } 

对于刚接触这些功能的人员来说, strlcatstrncat函数之间的区别并不完全明显。 strlcat函数将整个缓冲区大小作为第三个参数, strncat函数 缓冲区中的可用空间 大小作为参数,这需要在调用函数之前计算所需的值。 但是开发人员经常忘记或不知道。 将strncat函数传递整个缓冲区大小可能会导致缓冲区溢出,因为 函数将把该值视为允许复制的字符数。 strlcat函数存在此问题,但是要使其正常工作,必须传递以终端零结尾的行。

带有行的危险场所的完整列表:

  • V645'strncat'函数调用可能导致'输出'缓冲区溢出。 边界不应包含缓冲区的大小,而应包含可以容纳的多个字符。 命名空间转储.cpp 104
  • V645'strncat'函数调用可能导致'输出'缓冲区溢出。 The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 107
  • V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 110
  • V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 113
  • V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 118
  • V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 119
  • V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 120
  • V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 123
  • V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 126
  • V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 129
  • V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 132
  • V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 135
  • V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 138
  • V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 141
  • V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 144
  • V645 The 'strncat' function call could lead to the 'features_string' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. VirtioDevice.cpp 283
  • V645 The 'strncat' function call could lead to the 'features_string' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. VirtioDevice.cpp 284
  • V645'strncat'函数调用可能导致'features_string'缓冲区溢出。边界不应包含缓冲区的大小,而应包含可以容纳的多个字符。VirtioDevice.cpp 285

V792位于操作员'|'右边的'SetDecoratorSettings'功能 不管左操作数的值如何,都会被调用。也许最好使用“ ||”。桌面侦听器.cpp 324

 class DesktopListener : public DoublyLinkedListLinkImpl<DesktopListener> { public: .... virtual bool SetDecoratorSettings(Window* window, const BMessage& settings) = 0; .... }; bool DesktopObservable::SetDecoratorSettings(Window* window, const BMessage& settings) { if (fWeAreInvoking) return false; InvokeGuard invokeGuard(fWeAreInvoking); bool changed = false; for (DesktopListener* listener = fDesktopListenerList.First(); listener != NULL; listener = fDesktopListenerList.GetNext(listener)) changed = changed | listener->SetDecoratorSettings(window, settings); return changed; } 

最有可能的是,运算符'|' 和'||'。这样的错误导致对SetDecoratorSettings函数的不必要的调用

V627考虑检查表达式。sizeof()的参数是扩展为数字的宏。设备72

 #define PCI_line_size 0x0c /* (1 byte) cache line size in 32 bit words */ static status_t wb840_open(const char* name, uint32 flags, void** cookie) { .... data->wb_cachesize = gPci->read_pci_config(data->pciInfo->bus, data->pciInfo->device, data->pciInfo->function, PCI_line_size, sizeof(PCI_line_size)) & 0xff; .... } 

0x0c传递给sizeof运算符看起来很可疑。也许您应该计算某些对象的大小,例如dataV562比较布尔类型值和18:0x12 == IsProfessionalSpdif()。第533章



 typedef bool BOOL; virtual BOOL IsProfessionalSpdif() { ... } #define ECHOSTATUS_DSP_DEAD 0x12 ECHOSTATUS CEchoGals::ProcessMixerFunction(....) { .... if ( ECHOSTATUS_DSP_DEAD == IsProfessionalSpdif() ) // <= { Status = ECHOSTATUS_DSP_DEAD; } else { pMixerFunction->Data.bProfSpdif = IsProfessionalSpdif(); } .... } 

IsProfessionalSpdif函数将返回bool类型的,而在这种情况下,该函数的结果将与数字0x12进行比较

结论


我们错过了去年秋天发布的第一个Haiku测试版,因为正忙于发布Java语言的PVS-Studio。但是程序员错误的本质是,除非您寻找它们并且根本不关注代码的质量,否则它们不会到任何地方。开发人员尝试使用Coverity Scan,但是分析的最后一次运行是在大约两年前。这会使Haiku用户不高兴。尽管使用Coverity进行分析是在2014年进行的,但这并没有阻止我们在2015年撰写两篇带有错误评论的大型文章(第1 部分第2部分)。

那些读到最后的人都在等待对Haiku代码的再次审查,而Haiku代码的数量和新的错误一样多。完整的分析器报告将在发布代码审查之前发送给开发人员,因此可以修复一些错误。为了在出版物之间花费时间,建议您在项目上下载并尝试使用PVS-Studio

想尝试Haiku并有任何疑问吗? Haiku开发人员邀请您访问电报频道



如果您想与讲英语的读者分享这篇文章,请使用以下链接:Svyatoslav Razmyslov。 如何在C和C ++中放纵自己。 Haiku OS食谱

Source: https://habr.com/ru/post/zh-CN461255/


All Articles