关于PVS-Studio静态分析仪和Haiku OS代码如何相遇的故事可以追溯到2015年。对于两个项目的团队来说,这都是一次激动人心的实验和有益的经验。 为什么要进行实验? 那时,我们没有适用于Linux的分析仪,并且也将再有一年半的时间。 无论如何,我们团队的发烧友的努力得到了回报:我们与Haiku开发人员聚集在一起,提高了代码质量,利用开发人员制作的罕见错误扩大了错误基础,并完善了分析器。 现在,您可以轻松快捷地检查Haiku代码中的错误。
引言
了解我们故事的主要特征-带有开放源代码的
Haiku和针对C,C ++,C#和Java的
PVS-Studio静态分析器。 4.5年前,当我们深入研究项目分析时,我们只需要处理已编译的可执行分析器文件。 用于解析编译器参数,运行预处理器,并行分析等的所有基础结构均来自用C#编写的实用程序
Compiler Monitoring UI 。 该实用程序已部分移植到要在Linux上运行的Mono平台。 Haiku项目是在Windows以外的各种操作系统下使用交叉编译器构建的。 我想再次提到与Haiku大楼相关的便利性和文档完整性。 我还要感谢Haiku开发人员在构建项目中所提供的帮助。
现在执行分析要简单得多。 这是用于构建和分析项目的所有命令的列表:
cd /opt git clone https:
顺便说一下,项目分析是在Docker容器中实现的。 最近,我们准备了有关该主题的新文档:
在Docker中运行PVS-Studio 。 对于某些公司来说,这可以非常轻松地将静态分析技术应用于其项目。
未初始化的变量
V614使用了未初始化的变量“ rval”。 1727年
int auto_fetch(int argc, char *argv[]) { volatile int argpos; int 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;
这是使用未初始化变量的类似情况,除了
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;
错误初始化的另一个示例。 类字段按照其在类本身中的声明顺序进行初始化。 在此示例中,
fInternal字段将是第一个使用未初始化的
fPatternHandler值初始化的
字段 。
可疑的#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); }
分析器是正确的
-if和
else分支相同。 但是
mtx_lock_spin和
mtx_unlock_spin函数在哪里? 宏
TQ_LOCK ,
TQ_UNLOCK和
grouptask_block函数几乎在彼此相邻的一个文件中声明,但是仍然在此处的某个位置进行了替换。
搜索文件仅导致具有以下内容的
mutex.h :
#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函数中传递null指针,但是这种用法肯定是可疑的。 因此,分析器发现混合的代码行。 首先,只有在分配了
NULL之后,代码作者才必须通过
fVectorIcon指针释放内存。
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) {
这是将空指针显式传递给
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指针用于为字符数组分配内存。
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);
这里的
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:
除了在
delete /
delete []和
free之间选择错误之外,当您尝试通过指向void类型的指针
(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在这里不代表任何含义。 您需要将函数注释为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) {
分析器检测到指针抛出的
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; .... }
分析器发现无符号变量与负值的显式比较。 也许,应该只将
剩余的变量与null进行比较,或者实施溢出检查。
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)
为了得到错误的要点,让我们处理
一下sleep和
usleep函数的签名:
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);
设备对象由
删除操作员释放。 对于
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) { .... error = malo_hal_send_helper(mh, 0, NULL, 0, MALO_NOWAIT);
跨过程分析揭示了将
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可以检测到内存泄漏 。 在此示例中,如果发生错误,则不会释放内存。 有人可能会认为,如果出现错误,您不应该为内存释放而烦恼,因为程序仍然会结束。 但这并非总是如此。 许多程序都需要正确处理错误并继续工作。
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; .... } .... } .... }
对于不熟悉这些功能描述的人来说,
strlcat和
strncat函数之间的区别不是很明显。
strlcat函数将整个缓冲区的大小作为第三个参数,而
strncat函数-缓冲区中的可用空间的大小,这需要在调用函数之前评估所需的值。 但是开发人员经常忘记或不知道。 将整个缓冲区大小传递给
strncat函数可能会导致缓冲区溢出,因为该函数会将此值视为要复制的可接受字符数。
strlcat函数没有这样的问题。 但是您必须传递字符串,并以terminal null结尾,以使其正常工作。
这是带有字符串的危险场所的完整列表:
- V645'strncat'函数调用可能导致'输出'缓冲区溢出。 边界不应包含缓冲区的大小,而应包含可以容纳的多个字符。 命名空间转储.cpp 104
- V645'strncat'函数调用可能导致'输出'缓冲区溢出。 边界不应包含缓冲区的大小,而应包含可以容纳的多个字符。 命名空间转储.cpp 107
- V645'strncat'函数调用可能导致'输出'缓冲区溢出。 边界不应包含缓冲区的大小,而应包含可以容纳的多个字符。 命名空间转储.cpp 110
- V645'strncat'函数调用可能导致'输出'缓冲区溢出。 边界不应包含缓冲区的大小,而应包含可以容纳的多个字符。 命名空间转储.cpp 113
- V645'strncat'函数调用可能导致'输出'缓冲区溢出。 边界不应包含缓冲区的大小,而应包含可以容纳的多个字符。 命名空间转储.cpp 118
- V645'strncat'函数调用可能导致'输出'缓冲区溢出。 边界不应包含缓冲区的大小,而应包含可以容纳的多个字符。 命名空间转储.cpp 119
- V645'strncat'函数调用可能导致'输出'缓冲区溢出。 边界不应包含缓冲区的大小,而应包含可以容纳的多个字符。 命名空间转储.cpp 120
- V645'strncat'函数调用可能导致'输出'缓冲区溢出。 边界不应包含缓冲区的大小,而应包含可以容纳的多个字符。 命名空间转储.cpp 123
- V645'strncat'函数调用可能导致'输出'缓冲区溢出。 边界不应包含缓冲区的大小,而应包含可以容纳的多个字符。 命名空间转储.cpp 126
- V645'strncat'函数调用可能导致'输出'缓冲区溢出。 边界不应包含缓冲区的大小,而应包含可以容纳的多个字符。 命名空间转储.cpp 129
- V645'strncat'函数调用可能导致'输出'缓冲区溢出。 边界不应包含缓冲区的大小,而应包含可以容纳的多个字符。 命名空间转储.cpp 132
- V645'strncat'函数调用可能导致'输出'缓冲区溢出。 边界不应包含缓冲区的大小,而应包含可以容纳的多个字符。 命名空间转储.cpp 135
- V645'strncat'函数调用可能导致'输出'缓冲区溢出。 边界不应包含缓冲区的大小,而应包含可以容纳的多个字符。 命名空间转储.cpp 138
- V645'strncat'函数调用可能导致'输出'缓冲区溢出。 边界不应包含缓冲区的大小,而应包含可以容纳的多个字符。 命名空间转储.cpp 141
- V645'strncat'函数调用可能导致'输出'缓冲区溢出。 边界不应包含缓冲区的大小,而应包含可以容纳的多个字符。 命名空间转储.cpp 144
- V645'strncat'函数调用可能导致'features_string'缓冲区溢出。 边界不应包含缓冲区的大小,而应包含可以容纳的多个字符。 VirtioDevice.cpp 283
- V645'strncat'函数调用可能导致'features_string'缓冲区溢出。 边界不应包含缓冲区的大小,而应包含可以容纳的多个字符。 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 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运算符看起来很可疑。 也许,作者应该已经评估了对象的大小,例如
data 。
V562比较布尔类型值和18:0x12 == IsProfessionalSpdif()。 第533章
typedef bool BOOL; virtual BOOL IsProfessionalSpdif() { ... } #define ECHOSTATUS_DSP_DEAD 0x12 ECHOSTATUS CEchoGals::ProcessMixerFunction(....) { .... if ( ECHOSTATUS_DSP_DEAD == IsProfessionalSpdif() )
IsProfessionalSpdif函数返回
布尔类型值。 这样做时,将函数的结果与条件中的数字
0x12进行比较。
结论
由于我们正忙于发布Java的PVS-Studio,因此我们错过了去年秋天发布的第一个Haiku beta。 编程错误的本质仍然是,如果您不搜索它们并且不注意代码质量,它们也不会消失。 项目开发人员使用
Coverity Scan ,但是最近一次运行是在两年前。 这一定会让Haiku用户不高兴。 尽管分析是在2014年使用Coverity进行配置的,但这并没有阻止我们在2015年撰写两篇关于错误检查的长篇文章(
第1 部分 ,
第2部分 )
Haiku对错误的另一种评论即将发布,即将读完这篇文章的人一听到底。 完整的分析器报告将在发布此错误评论之前发送给开发人员,因此在阅读本文时,某些错误可能已修复。 为了在文章之间花费时间,我建议为您的项目下载并尝试使用
PVS-Studio 。
您想尝试Haiku吗? Haiku开发人员邀请您加入
电报频道 。