
现代计算机技术,技术和软件解决方案-所有这些都极大地促进和加速了各种科学研究的实施。 通常,计算机仿真是检验许多理论的唯一方法。 科学软件具有自己的特点。 例如,此类软件通常会经过非常全面的测试,但记录不充分。 但是,软件是人们编写的,人们会犯错误。 科学程序中的错误可能会对整个研究产生怀疑。 本文将列出在NCBI Genome Workbench软件包的代码中发现的许多问题。
引言
NCBI基因组工作台为研究人员提供了广泛的工具,用于研究和分析遗传数据。 用户可以研究和比较来自多个来源的数据,包括NCBI(国家生物技术信息中心)数据库或他们自己的个人数据。
如前所述,单元测试通常涵盖科学软件。 检查该项目时,分析中排除了包含测试文件的85个目录。 这大约是一千个文件。 也许这是由于需要测试用于各种研究而发明的各种复杂算法。 但是其余代码(不是测试代码)的质量却没有我们想要的那么高。 但是,就像在尚未考虑引入静态代码分析工具的任何项目中一样:)。
静态代码分析器为C / C ++ / C#/ Java-
PVS-Studio提供了用于代码审查(或研究)的数据。
只有两个数字会破坏您的项目
基于我们的错误数据库,当前有超过一万两千个示例被选择,我们注意到并描述了编写导致大量错误的代码的特定模式。 例如,我们进行了以下研究:
- 最后一行的效果 ;
- C / C ++世界上最危险的函数 ;
- C / C ++中的逻辑表达式。 专业人士有多错误 ;
- 邪恶生活在比较功能中 。
该项目标志着新模式描述的开始。 我们正在谈论变量名称中的数字
1和
2 ,例如
file1和
file2等。 容易混淆两个这样的变量。 这是代码中拼写错误的一种特殊情况,但是一个这样的错误导致人们希望使用同名变量,它们之间的区别仅在于名称末尾的数字1和2。
展望未来,我会说上面列出的所有研究都已在该项目的代码D中得到确认。
考虑Genome Workbench项目的第一个示例:
V501在“ ||”的左侧和右侧有相同的子表达式“(!Loc1.IsInt()&&!Loc1.IsWhole())” 操作员。 nw_aligner.cpp 480
CRef<CSeq_align> CNWAligner::Run(CScope &scope, const CSeq_loc &loc1, const CSeq_loc &loc2, bool trim_end_gaps) { if ((!loc1.IsInt() && !loc1.IsWhole()) || (!loc1.IsInt() && !loc1.IsWhole())) { NCBI_THROW(CException, eUnknown, "Only whole and interval locations supported"); } .... }
我们看到两个名为
loc1和
loc2的变量。 代码中还有一个错误:未使用
loc2变量,因为代替了它,再次使用了
loc1 。
另一个例子:
V560条件表达式的一部分始终为false:s1.IsSet()。 valid_biosource.cpp 3073
static bool s_PCRPrimerSetLess(const CPCRPrimerSet& s1, const CPCRPrimerSet& s2) { if (!s1.IsSet() && s1.IsSet()) { return true; } else if (s1.IsSet() && !s2.IsSet()) { return false; } else if (!s1.IsSet() && !s2.IsSet()) { return false; } else if (s1.Get().size() < s2.Get().size()) { return true; } else if (s1.Get().size() > s2.Get().size()) { return false; } else { ..... }
代码的第一行将变量
s1和
s2混合在一起。 根据名称,这是一个比较功能。 但是这种错误可能随处可见,因为通过命名变量
Number 1和
Number 2 ,程序员几乎肯定会在将来犯一个错误。 并且在函数中使用此类名称的次数越多,出错的可能性就越高。
其他错别字和复制粘贴
V501 '!='运算符左右两侧有相同的子表达式:bd.bit_.bits [i]!= Bd.bit_.bits [i] bm.h 296
bool compare_state(const iterator_base& ib) const { .... if (this->block_type_ == 0 { if (bd.bit_.ptr != ib_db.bit_.ptr) return false; if (bd.bit_.idx != ib_db.bit_.idx) return false; if (bd.bit_.cnt != ib_db.bit_.cnt) return false; if (bd.bit_.pos != ib_db.bit_.pos) return false; for (unsigned i = 0; i < bd.bit_.cnt; ++i) { if (bd.bit_.bits[i] != bd.bit_.bits[i]) return false; } } .... }
我相信经过所有检查之后,
bd.bit_和
ib_db.bit_对象的
位数组的大小是相等的。 因此,该代码的作者编写了一个循环来对位数组进行元素比较,但以其中一个比较对象的名称打错了
字 。 结果,在某些情况下,被比较的对象可能被错误地认为是相等的。
这个例子值得“
邪恶生活在比较功能中 ”一文。
V501在“ ||”的左侧和右侧有相同的子表达式“ CFieldHandler :: QualifierNamesAreEquivalent(字段,kFieldTypeSeqId)” 操作员。 field_handler.cpp 152
bool CFieldHandlerFactory::s_IsSequenceIDField(const string& field) { if ( CFieldHandler::QualifierNamesAreEquivalent(field, kFieldTypeSeqId) || CFieldHandler::QualifierNamesAreEquivalent(field, kFieldTypeSeqId)) { return true; } else { return false; } }
最有可能的检查是多余的。 我没有在代码变量中找到类似于
kFieldTypeSeqId的变量。 但是,由于使用“ ||”运算符,可能会导致额外的函数调用,从而降低性能。
另外两个带有分析仪警告的相同类型的场所,需要进行验证:
- V501在'&&'运算符的左侧和右侧有相同的子表达式'uf-> GetData()。IsBool()'。 variant_utils.cpp 1711
- V501在'&&'运算符的左侧和右侧有相同的子表达式'uf-> GetData()。IsBool()'。 variant_utils.cpp 1735
V766已经添加了具有相同键“ kArgRemote”的项目。 blast_args.cpp 3262
void CBlastAppArgs::x_IssueWarningsForIgnoredOptions(const CArgs& args) { set<string> can_override; .... can_override.insert(kArgOutputFormat); can_override.insert(kArgNumDescriptions); can_override.insert(kArgNumAlignments); can_override.insert(kArgMaxTargetSequences); can_override.insert(kArgRemote);
分析仪检测到向
设置的容器添加了两个相同的值。 回想一下,该容器仅存储唯一值,因此不会向其添加重复项。
类似于上述代码的代码通常是使用copy-paste方法编写的。 可能仅仅是一个额外的值,或者也许作者在复制时忘记重命名其中一个变量。 当您删除一个额外的
插入调用时
,代码会稍作优化,但这并不重要。 更重要的是,由于集合中缺少元素,可能会在这里隐藏严重错误。
V523'then '语句等效于后续代码片段。 vcf_reader.cpp 1105
bool CVcfReader::xAssignFeatureLocationSet(....) { .... if (data.m_SetType == CVcfData::ST_ALL_DEL) { if (data.m_strRef.size() == 1) {
该函数包含大型且完全相同的代码片段。 但是,它们包含各种附带的注释。 该代码的编写不是最佳,令人困惑的,并且可能包含错误。
带有if-else语句的可疑场所的完整列表如下所示:
- V523'then'语句等效于'else'语句。 第2142章
- V523'then'语句等效于后续代码片段。 odbc.c 379
- V523'then'语句等效于后续代码片段。 odbc.c 1414
- V523'then'语句等效于'else'语句。 seqdbvol.cpp 1922年
- V523'then'语句等效于'else'语句。 seqdb_demo.cpp 466
- V523'then'语句等效于后续代码片段。 blast_engine.c 1917年
- V523'then'语句等效于'else'语句。 blast_filter.c 420
- V523'then'语句等效于'else'语句。 blast_parameters.c 636
- V523'then'语句等效于'else'语句。 第684章
- V523'then'语句等效于'else'语句。 bme.cpp 333
- V523'then'语句等效于'else'语句。 gme.cpp 484
/ *具有安全性的最好是书呆子* /
V597编译器可以删除“
内存集 ”函数调用,该函数调用用于刷新“ passwd_buf”缓冲区。 memset_s()函数应用于擦除私有数据。 第366章
void tds_answer_challenge(....) { #define MAX_PW_SZ 14 .... if (ntlm_v == 1) { .... memset(hash, 0, sizeof(hash)); memset(passwd_buf, 0, sizeof(passwd_buf)); memset(ntlm2_challenge, 0, sizeof(ntlm2_challenge)); } else { .... } }
您可能已经猜到了,在节标题中使用了关于代码安全性的有趣注释。
简而言之,由于不再使用刷新缓冲区,因此编译器将删除
memset函数。 而且像
hash或
passwd_buf这样的数据实际上不会为零。 有关此非显而易见的编译器机制的更多信息,请参见文章“
安全清除私有数据 ”。
V597编译器可以删除“
内存集 ”函数调用,该函数调用用于刷新“答案”对象。 memset_s()函数应用于擦除私有数据。 第561章
static TDSRET tds7_send_auth(....) { .... memset(&answer, 0, sizeof(TDSANSWER)); return tds_flush_packet(tds); }
那不是唯一评论“安全”的例子。 从评论来看,可以假设安全性对于该项目确实非常重要。 因此,我附上已发现问题的整个非小清单:
- V597编译器可能会删除“ memset”函数调用,该函数调用用于刷新“堆”对象。 memset_s()函数应用于擦除私有数据。 ncbi_heapmgr.c 1300
- V597编译器可以删除“内存集”函数调用,该函数调用用于刷新“上下文”对象。 memset_s()函数应用于擦除私有数据。 第167章
- V597编译器可以删除“内存集”函数调用,该函数调用用于刷新“ ks”对象。 memset_s()函数应用于擦除私有数据。 挑战c 339
- V597编译器可以删除“内存集”函数调用,该函数调用用于刷新“ md5_ctx”对象。 memset_s()函数应用于擦除私有数据。 第353章
- V597编译器可以删除“内存集”函数调用,该函数调用用于刷新“哈希”缓冲区。 memset_s()函数应用于擦除私有数据。 Challenge.c 365
- V597编译器可以删除“内存集”函数调用,该函数调用用于刷新“ ks”对象。 memset_s()函数应用于擦除私有数据。 挑战c 406
- V597编译器可以删除“内存集”函数调用,该函数调用用于刷新“ ntlm_v2_response”对象。 memset_s()函数应用于擦除私有数据。 login.c 795
- V597编译器可以删除“内存集”函数调用,该函数调用用于刷新“答案”对象。 memset_s()函数应用于擦除私有数据。 login.c 801
- V597编译器可以删除“内存集”函数调用,该函数调用用于刷新“数据包”缓冲区。 memset_s()函数应用于擦除私有数据。 数值c 256
- V597编译器可以删除“内存集”函数调用,该函数调用用于刷新“数据包”缓冲区。 memset_s()函数应用于擦除私有数据。 数字c 110
- V597编译器可能会删除“ memset”函数调用,该函数调用用于刷新“ pwd”缓冲区。 memset_s()函数应用于擦除私有数据。 getpassarg.c 50
- V597编译器可以删除“内存集”函数调用,该函数调用用于刷新“上下文”对象。 memset_s()函数应用于擦除私有数据。 挑战c 188
- V597编译器可能会删除“ memset”函数调用,该函数调用用于刷新“ buf”缓冲区。 memset_s()函数应用于擦除私有数据。 第243章
- V597编译器可以删除“内存集”函数调用,该函数调用用于刷新“ ntlm_v2_hash”缓冲区。 memset_s()函数应用于擦除私有数据。 挑战c 309
- V597编译器可以删除“内存集”函数调用,该函数调用用于刷新“ md5_ctx”对象。 memset_s()函数应用于擦除私有数据。 挑战c 354
- V597编译器可以删除“内存集”函数调用,该函数调用用于刷新“ passwd_buf”缓冲区。 memset_s()函数应用于擦除私有数据。 挑战c 380
- V597编译器可以删除“内存集”函数调用,该函数调用用于刷新“ ks”对象。 memset_s()函数应用于擦除私有数据。 第393章
- V597编译器可以删除“内存集”函数调用,该函数调用用于刷新“哈希”缓冲区。 memset_s()函数应用于擦除私有数据。 挑战c 394
- V597编译器可以删除“内存集”函数调用,该函数调用用于刷新“ ntlm2_challenge”缓冲区。 memset_s()函数应用于擦除私有数据。 挑战c 395
- V597编译器可以删除“内存集”函数调用,该函数调用用于刷新“ ks”对象。 memset_s()函数应用于擦除私有数据。 挑战c 419
- V597编译器可以删除“内存集”函数调用,该函数调用用于刷新“ ntlm_v2_response”对象。 memset_s()函数应用于擦除私有数据。 第556章
可疑周期
V534可能在“ for”运算符内比较了错误的变量。 考虑查看“ i”。 taxFormat.cpp 569
void CTaxFormat::x_LoadTaxTree(void) { .... for(size_t i = 0; i < alignTaxids.size(); i++) { int tax_id = alignTaxids[i]; .... for(size_t j = 0; i < taxInfo.seqInfoList.size(); j++) { SSeqInfo* seqInfo = taxInfo.seqInfoList[j]; seqInfo->taxid = newTaxid; } .... } .... }
我认为,在内部循环的条件下,变量是
我随机获得的。 相反,应该使用变量
j 。
V535变量“ i”用于该循环和外部循环。 检查行:302、309。sls_alp.cpp 309
alp::~alp() { .... if(d_alp_states) { for(i=0;i<=d_nalp;i++)
两个嵌套的相同循环(其中还重置了全局计数器)看起来非常可疑。 开发人员应检查此处发生的情况。
数组索引异常
V520数组索引表达式'[-i2,-k]'中的逗号运算符','。 nw_spliced_aligner16.cpp 564
void CSplicedAligner16::x_DoBackTrace ( const Uint2* backtrace_matrix, CNWAligner::SAlignInOut* data, int i_global_max, int j_global_max) { .... while(intron_length < m_IntronMinSize || (Key & donor) == 0) { Key = backtrace_matrix[--i2, --k]; ++intron_length; data->m_transcript.push_back(eTS_Intron); } .... }
我必须马上说,似乎没有错误(目前,大声笑)。 考虑以下行:
Key = backtrace_matrix[--i2, --k];
单词“矩阵”和双索引可能暗示该数组是二维的,但不是二维的。 这是指向整数数组的常规指针。 但是
V520诊断程序并没有出现。 程序员对于如何索引二维数组确实感到困惑。
在这种情况下,作者只是决定保存一行代码,尽管他可以这样写:
--i2; Key = backtrace_matrix[--k];
V661可疑表达式'A [B == C]'。 可能是“ A [B] == C”。 ncbi_service_connector.c 180
static EHTTP_HeaderParse s_ParseHeader(const char* header, ....) { .... if (sscanf(header, "%u.%u.%u.%u%n", &i1, &i2, &i3, &i4, &n) < 4 || sscanf(header + n, "%hu%x%n", &uuu->port, &tkt, &m) < 2 || (header[m += n] && !(header[m] == '$') && !isspace((unsigned char)((header + m) [header[m] == '$'])))) { break; } .... }
我花了很长时间试图了解正在发生的事情的另一个代码示例:D.
isspace()函数检查索引为
m的字符,但是如果此字符为'$',则索引为
m +1的字符
将传递给该函数。 此外,与“ $”的比较已经提前。 也许这里没有错误,但是绝对可以更清楚地重写代码。
V557阵列可能超限。 “行”索引指向数组边界之外。 aln_reader.cpp 412
bool CAlnReader::x_IsGap(TNumrow row, TSeqPos pos, const string& residue) { if (m_MiddleSections.size() == 0) { x_CalculateMiddleSections(); } if (row > m_MiddleSections.size()) { return false; } if (pos < m_MiddleSections[row].first) { .... } .... }
这里有一个严重的错误。 正确的
行索引检查应如下所示:
if (row >= m_MiddleSections.size()) { return false; }
否则,可以访问
MiddleSections向量外部的数据。
还有更多这样的地方:
- V557阵列可能超限。 “ i”索引指向数组界限之外。 resource_pool.hpp 388
- V557阵列可能超限。 “行”索引指向数组边界之外。 aln_reader.cpp 418
- V557阵列可能超限。 “ fmt_idx”索引指向数组界限之外。 seq_writer.cpp 384
- V557阵列可能超限。 “ fmt_idx”索引指向数组界限之外。 blastdb_formatter.cpp 183
- V557阵列可能超限。 'num'索引指向数组边界之外。 newcleanupp.cpp 13035
如何获得对功能的不信任
V570'm_onClickFunction'变量被分配给它自己。 alngraphic.hpp 103
void SetOnClickFunctionName(string onClickFunction) { m_onClickFunction = m_onClickFunction; }
甚至没有什么可评论的。 您只能同情单击某物,单击某物但没有任何变化的人。
将变量分配给自己的另外两种情况将产生一个列表:
- V570将'iter-> level'变量分配给它自己。 align_format_util.cpp 189
- V570将'd_elements_values [ind]'变量分配给它自己。 sls_alp_data.cpp 1416
V763参数“ w1”在使用前总是在功能体中重写。 bmfunc.h 5363
在进入函数后立即磨损参数的函数可能会误导使用它的开发人员。 该代码应仔细检查。
类设计错误
V688'm_qsrc '函数参数具有与类成员之一相同的名称,这可能导致混淆。 第873章
class CElementaryMatching: public CObject { .... ISequenceSource * m_qsrc; .... void x_CreateIndex (ISequenceSource *m_qsrc, EIndexMode index_more, ....); void x_CreateRemapData(ISequenceSource *m_qsrc, EIndexMode mode); void x_LoadRemapData (ISequenceSource *m_qsrc, const string& sdb); .... };
立即有3个类函数包含名称与类字段一致的参数。 这可能导致函数体出错:程序员可能会认为他正在与一个类成员一起工作,实际上是在更改局部变量的值。
V614使用了未初始化的变量'm_BitSet'。 SnpBitAttributes.hpp 187
构造函数之一可
随意使用
m_BitSet变量。 事实是该变量未初始化。 其“垃圾”值在循环的第一次迭代中使用,此后发生初始化。 这是一个非常严重的错误,导致未定义的程序行为。
V603对象已创建,但未被使用。 如果要调用构造函数,则应使用“ this-> SIntervalComparisonResult :: SIntervalComparisonResult(....)”。 compare_feats.hpp 100
很久以前,在检查项目时我没有遇到这样的错误。 但是问题仍然存在。 错误是,以这种方式调用参数化构造函数会创建并删除一个临时对象。 并且类字段保持未初始化。 应该通过初始化列表调用另一个构造函数(请参阅
Delegating构造函数 )。
V591非无效函数应返回一个值。 第266章
分析器认为该行在重载语句中丢失:
return *this;
V670未初始化的类成员'm_OutBlobIdOrData'用于初始化'm_StdOut'成员。 请记住,成员是按照类内声明的顺序进行初始化的。 remote_app.hpp 215
class NCBI_XCONNECT_EXPORT CRemoteAppResult { public: CRemoteAppResult(CNetCacheAPI::TInstance netcache_api, size_t max_inline_size = kMaxBlobInlineSize) : m_NetCacheAPI(netcache_api), m_RetCode(-1), m_StdOut(netcache_api, m_OutBlobIdOrData, m_OutBlobSize), m_OutBlobSize(0), m_StdErr(netcache_api, m_ErrBlobIdOrData, m_ErrBlobSize), m_ErrBlobSize(0), m_StorageType(eBlobStorage), m_MaxInlineSize(max_inline_size) { } .... };
系统会立即对此代码段发出3个分析器警告。 类字段的初始化不是按照它们在初始化列表中列出的顺序进行,而是按照它们在类中声明的方式进行。 导致此错误的经典原因是,并非所有程序员都记得或知道此规则。 在这里和初始化列表中的顺序是错误的。 人们会感觉到字段列表是以随机顺序输入的。
V746对象切片。 应该通过引用而不是值来捕获异常。 钴cpp 247
void CMultiAligner::SetQueries(const vector< CRef<objects::CBioseq> >& queries) { .... try { seq_loc->SetId(*it->GetSeqId()); } catch (objects::CObjMgrException e) { NCBI_THROW(CMultiAlignerException, eInvalidInput, (string)"Missing seq-id in bioseq. " + e.GetMsg()); } m_tQueries.push_back(seq_loc); .... }
由于创建新对象,按值捕获异常可能导致丢失有关该异常的某些信息。 通过引用捕获异常会更好,更安全。
类似的地方:
- V746对象切片。 应该通过引用而不是值来捕获异常。 agp_validate_reader.cpp 562
- V746对象切片。 应该通过引用而不是值来捕获异常。 aln_build_app.cpp 320
- V746对象切片。 应该通过引用而不是值来捕获异常。 458第458章
- V746对象切片。 应该通过引用而不是值来捕获异常。 钴cpp 691
- V746对象切片。 应该通过引用而不是值来捕获异常。 钴cpp 719
- V746对象切片。 应该通过引用而不是值来捕获异常。 钴cpp 728
- V746对象切片。 应该通过引用而不是值来捕获异常。 钴cpp 732
关于无法访问的代码和其他代码执行问题
V779检测到无法访问的代码。 可能存在错误。 merge_tree_core.cpp 627
bool CMergeTree::x_FindBefores_Up_Iter(....) { .... FirstFrame->Curr = StartCurr; FirstFrame->Returned = false; FirstFrame->VisitCount = 0; FrameStack.push_back(FirstFrame); while(!FrameStack.empty()) { .... if(Rel == CEquivRange::eAfter) { Frame->Returned = false; FrameStack.pop_back(); continue; } else if(Rel == CEquivRange::eBefore) { .... continue; } else { if(Frame->VisitCount == 0) { .... continue; } else { .... continue; } } Frame->Returned = false;
编写条件语句的代码,以便代码的绝对所有分支都以
continue语句结尾。 这导致在
while循环中形成几行无法访问的代码。 这些行看起来非常可疑。 最有可能的是,此问题在重构代码后出现,现在需要仔细的代码审查。
V519连续两次给'interval_width'变量赋值两次。 也许这是一个错误。 检查行:454、456。aln_writer.cpp 456
void CAlnWriter::AddGaps(....) { .... switch(exon_chunk->Which()) { case CSpliced_exon_chunk::e_Match: interval_width = exon_chunk->GetMatch(); case CSpliced_exon_chunk::e_Mismatch: interval_width = exon_chunk->GetMismatch(); case CSpliced_exon_chunk::e_Diag: interval_width = exon_chunk->GetDiag(); genomic_string.append(....); product_string.append(....); genomic_pos += interval_width; product_pos += interval_width/res_width; break; .... } .... }
变量
interval_width被多次覆盖,因为 在
case分支中没有
break语句。 虽然是经典的,但是却是一个非常糟糕的错误。
其他一些可疑的地方:
- V779检测到无法访问的代码。 可能存在错误。 dbapi_driver_utils.cpp 351
- V779检测到无法访问的代码。 可能存在错误。 网络780
- V779检测到无法访问的代码。 可能存在错误。 bcp.c 1495
- V779检测到无法访问的代码。 可能存在错误。 remote_blast.cpp 1470
- V779检测到无法访问的代码。 可能存在错误。 remote_blast.cpp 1522
V571定期检查。 “ if(m_QueryOpts-> filtering_options)”条件已经在第703行中进行了验证。blast_options_local_priv.hpp 713
inline void CBlastOptionsLocal::SetFilterString(const char* f) { .... if (m_QueryOpts->filtering_options)
显然,
else分支需要重写。 我有一些想使用
m_QueryOpts-> filtering_options指针的
想法 ,但是代码仍然在某种程度上令人困惑。 我呼吁代码的作者。
好吧,这个问题并不孤单:
- V571定期检查。 “ if(睡眠时间)”条件已在第205行中得到验证。request_control.cpp 208
- V571定期检查。 “ if(assignValue.empty())”条件已在第712行中得到验证。classstr.cpp 718
数据读取错误
V739 EOF不应与“ char”类型的值进行比较。 “ linestring [0]”应为“ int”类型。 第3509章
static EBool s_AfrpInitLineData( .... char* linestring = readfunc (pfile); .... while (linestring != NULL && linestring [0] != EOF) { s_TrimSpace (&linestring); .... } .... }
计划与EOF比较的
字符不应存储在
char变量中。 否则,存在值0xFF(255)的字符将变为-1并以与文件末尾(EOF)相同的方式进行解释的风险。 同样(以防万一)值得检查
readfunc函数的实现。
V663可能出现无限循环。 'cin.eof()'条件不足以使它脱离循环。
考虑将'cin.fail()'函数调用添加到条件表达式中。ncbicgi.cpp 1564 typedef std::istream CNcbiIstream; void CCgiRequest::Serialize(CNcbiOstream& os) const { .... CNcbiIstream* istrm = GetInputStream(); if (istrm) { char buf[1024]; while(!istrm->eof()) { istrm->read(buf, sizeof(buf)); os.write(buf, istrm->gcount()); } } }
分析仪检测到潜在错误,由于该错误可能发生无限循环。如果在读取数据时发生故障,则调用eof()函数将始终返回false。为了在这种情况下完成循环,必须对fail()函数返回的值进行额外检查。杂项错误
V502也许'?:'运算符的工作方式与预期的不同。'?:'运算符的优先级低于'&&'运算符。ncbi_connutil.c 1135 static const char* x_ClientAddress(const char* client_host, int local_host) { .... if ((client_host == c && x_IsSufficientAddress(client_host)) || !(ip = *c && !local_host ? SOCK_gethostbyname(c) : SOCK_GetLocalHostAddress(eDefault)) || SOCK_ntoa(ip, addr, sizeof(addr)) != 0 || !(s = (char*) malloc(strlen(client_host) + strlen(addr) + 3))) { return client_host; } .... }
注意表达式: !local_host ? SOCK_gethostbyname(c) : SOCK_GetLocalHostAddress(eDefault)
它不是按程序员期望的那样计算的,因为整个表达式如下所示: ip = *c && !local_host ? SOCK_gethostbyname(c) : SOCK_GetLocalHostAddress(...)
&&运算符的优先级高于?:。因此,代码无法按预期执行。V561给“ seq”变量赋值可能比重新声明它更好。先前的声明:validator.cpp,第490行。validator.cpp492 bool CValidator::IsSeqLocCorrectlyOrdered(const CSeq_loc& loc, CScope& scope) { CBioseq_Handle seq; try { CBioseq_Handle seq = scope.GetBioseqHandle(loc); } catch (CObjMgrException& ) {
由于程序员在try / catch部分中声明了一个新的seq变量,因此另一个seq变量仍未初始化,在下面的代码中使用。V562将布尔类型值与0进行比较是奇怪的:(((status)&0x7f)== 0)!= 0. ncbi_process.cpp 111 bool CProcess::CExitInfo::IsExited(void) const { EXIT_INFO_CHECK; if (state != eExitInfo_Terminated) { return false; } #if defined(NCBI_OS_UNIX) return WIFEXITED(status) != 0; #elif defined(NCBI_OS_MSWIN)
一切都没有预兆,但事实证明WIFEXITED是一个宏大的开头: return (((status) & 0x7f) == 0) != 0;
事实证明,该函数返回相反的值。在代码中,还有另一个这样的函数,它发出警告:- V562将布尔类型值与0进行比较是奇怪的。ncbi_process.cpp 126
V595在针对nullptr对其进行验证之前,已使用了'dst_len'指针。检查行:309、315。zlib.cpp 309 bool CZipCompression::CompressBuffer( const void* src_buf, size_t src_len, void* dst_buf, size_t dst_size, size_t* dst_len) { *dst_len = 0;
dst_len指针在函数的最开始处被取消引用,同时进一步检查代码是否等于零。如果dst_len指针为nullptr,则代码中发生错误,导致未定义的行为。V590考虑检查'ch!='\ 0'&& ch =='''表达式。表达式过多或打印错误。cleanup_utils.cpp 580 bool Asn2gnbkCompressSpaces(string& val) { .... while (ch != '\0' && ch == ' ') { ptr++; ch = *ptr; } .... }
停止循环的条件仅取决于字符ch是否为空格。该表达式可以简化为以下形式: while (ch == ' ') { .... }
结论
在科学研究中使用计算机程序可以帮助并且将有助于发现。我们希望不要因为错别字而错过特别重要的内容。我邀请NCBI Genome Workbench项目的开发人员与我们联系,我们将提供由PVS-Studio分析仪发布的完整报告。希望这个小小的代码研究可以帮助修复许多错误,并总体上提高项目的可靠性。如果尚未在项目代码上运行PVS-Studio,请尝试这样做。您可能会喜欢它:)。
如果您想与说英语的读者分享这篇文章,请使用以下链接:Svyatoslav Razmyslov。NCBI基因组工作台:受威胁的科学研究