遵循计算器的踪迹:Qalculate


早些时候,我们对大型数学程序包(例如Scilab和Octave)进行了代码审查,而计算器作为小型实用程序而被搁置一旁,由于它们的小型代码而难以出错。 我们误以为没有注意他们。 Windows计算器的源代码发布的案例表明,每个人都对讨论隐藏在其中的错误感兴趣,并且那里有足够的错误来撰写有关该错误的文章。 我和我的同事决定检查一些流行的计算器的代码,结果发现Windows计算器的代码还不错(破坏器)。

引言


夸口! -通用的跨平台计算器。 它易于使用,但提供了复杂数学软件包中通常具有的功能和多功能性,以及满足日常需求(例如货币换算和利息计算)的有用工具。 该项目包含两个组件: libqalculate (库和CLI)和qalculate-gtk (GTK + UI)。 该研究仅涉及libqalculate代码。

为了更方便地将项目与我们最近检查过的Windows计算器进行比较,我给出了libqalculate的Cloc实用程序的输出:

图片4

从主观上讲,存在更多错误,并且比Windows计算器的代码中的错误更严重。 但我建议您通过阅读此代码回顾自己得出结论。

顺便说一下,这里是指向有关检查Microsoft计算器的文章的链接:“ 计算Windows计算器中的错误。

PVS-Studio被用作静态分析工具。 这是一组用于代码质量控制,搜索错误和潜在漏洞的解决方案。 支持的语言包括:C,C ++,C#和Java。 该分析仪可以在Windows,Linux和macOS上启动。

再次复制粘贴和错别字!


V523'then '语句等效于'else'语句。 编号.cc 4018

bool Number::square() { .... if(mpfr_cmpabs(i_value->internalLowerFloat(), i_value->internalUpperFloat()) > 0) { mpfr_sqr(f_tmp, i_value->internalLowerFloat(), MPFR_RNDU); mpfr_sub(f_rl, f_rl, f_tmp, MPFR_RNDD); } else { mpfr_sqr(f_tmp, i_value->internalLowerFloat(), MPFR_RNDU); mpfr_sub(f_rl, f_rl, f_tmp, MPFR_RNDD); } .... } 

ifelse语句中的代码完全相同。 相邻的代码片段与此非常相似,但是它们使用不同的功能: internalLowerFloat()internalUpperFloat() 。 可以安全地假设程序员在此处复制了代码,却忘记了更正函数名称。

V501在“ ||”的左侧和右侧有相同的子表达式“!Mtr2.number()。IsReal()”。 操作员。 BuiltinFunctions.cc 6274

 int IntegrateFunction::calculate(....) { .... if(!mtr2.isNumber() || !mtr2.number().isReal() || !mtr.isNumber() || !mtr2.number().isReal()) b_unknown_precision = true; .... } 

这里出现了重复的表达式,因为它们在一个地方而不是名称mtr编写了mtr2 。 因此,在这种情况下,不会调用mtr.number()。IsReal()函数。

V501在“ ||”的左侧和右侧有相同的子表达式“ vargs [1] .representsNonPositive()”。 操作员。 BuiltinFunctions.cc 5785

图片6



在此代码中手动查找异常是不现实的! 但是他们是。 而且,在原始文件中,这些片段写在一行中。 分析器检测到重复表达式vargs [1] .representsNonPositive() ,这可能表示输入错误,因此可能表示错误。

这是您几乎找不到的全部可疑地点列表:

  • V501在“ ||”的左侧和右侧有相同的子表达式“ vargs [1] .representsNonPositive()”。 操作员。 BuiltinFunctions.cc 5788
  • V501在“ &&”运算符的左侧和右侧有相同的子表达式“ append”。 MathStructure.cc 1780
  • V501在“ &&”运算符的左侧和右侧有相同的子表达式“ append”。 MathStructure.cc 2043年
  • V501在'&&'运算符的左侧和右侧有相同的子表达式'(* * v_subs [v_order [1]]。RepresentsNegative(true)'。 MathStructure.cc 5569

无效的循环


V534可能在“ for”运算符内比较了错误的变量。 考虑查看“ i”。 MathStructure.cc 28741

 bool MathStructure::isolate_x_sub(....) { .... for(size_t i = 0; i < mvar->size(); i++) { if((*mvar)[i].contains(x_var)) { mvar2 = &(*mvar)[i]; if(mvar->isMultiplication()) { for(size_t i2 = 0; i < mvar2->size(); i2++) { if((*mvar2)[i2].contains(x_var)) {mvar2 = &(*mvar2)[i2]; break;} } } break; } } .... } 

在内部循环中,计数器是变量i2 ,但是由于输入错误,发生了错误-在停止循环的情况下,使用了来自外部循环的变量i

冗余还是错误?


V590考虑检查此表达式。 表达式过多或打印错误。 编号CC 6564

 bool Number::add(const Number &o, MathOperation op) { .... if(i1 >= COMPARISON_RESULT_UNKNOWN && (i2 == COMPARISON_RESULT_UNKNOWN || i2 != COMPARISON_RESULT_LESS)) return false; .... } 

在看了这样的代码之后,三年前,我写了一条笔记来帮助自己和其他程序员:“ C / C ++中的逻辑表达式。专业人士怎么错了 。” 遇到这样的代码后,我确信该注释一点也没有变得不那么重要。 您可以在文章中查找,找到与代码相对应的错误模式,并找出所有细微差别。

在此示例的情况下,转到“表达式== ||”部分 !=”并且我们了解到表达式i2 == COMPARISON_RESULT_UNKNOWN不会影响任何内容。

取消引用未验证的指针


V595在对nullptr进行验证之前,已使用了'o_data'指针。 检查行:1108、1112。DataSet.cc 1108

 string DataObjectArgument::subprintlong() const { string str = _("an object from"); str += " \""; str += o_data->title(); // <= str += "\""; DataPropertyIter it; DataProperty *o = NULL; if(o_data) { // <= o = o_data->getFirstProperty(&it); } .... } 

一个函数中的o_data指针被取消引用,而无需检查和检查。 这可能是冗余代码,也可能是潜在错误。 我倾向于最后一种选择。

还有两个类似的地方:

  • V595在对nullptr进行验证之前,已使用了'o_assumption'指针。 检查行:229,230。Variable.cc 229
  • V595在针对nullptr对其进行验证之前,已使用了'i_value'指针。 检查行:3412,3427。Number.cc 3412

free()或删除[]?


V611使用“ new”运算符分配了内存,但使用“ free”功能释放了内存。 考虑检查“ remcopy”变量后面的操作逻辑。 cc 8123号

 string Number::print(....) const { .... while(!exact && precision2 > 0) { if(try_infinite_series) { remcopy = new mpz_t[1]; // <= mpz_init_set(*remcopy, remainder); } mpz_mul_si(remainder, remainder, base); mpz_tdiv_qr(remainder, remainder2, remainder, d); exact = (mpz_sgn(remainder2) == 0); if(!started) { started = (mpz_sgn(remainder) != 0); } if(started) { mpz_mul_si(num, num, base); mpz_add(num, num, remainder); } if(try_infinite_series) { if(started && first_rem_check == 0) { remainders.push_back(remcopy); } else { if(started) first_rem_check--; mpz_clear(*remcopy); free(remcopy); // <= } } .... } .... } 

remcopy阵列的内存以各种方式分配和释放,这是一个严重的错误。

遗失的变更


 bool expand_partial_fractions(MathStructure &m, ....) { .... if(b_poly && !mquo.isZero()) { MathStructure m = mquo; if(!mrem.isZero()) { m += mrem; m.last() *= mtest[i]; m.childrenUpdated(); } expand_partial_fractions(m, eo, false); return true; } .... } 

变量m在函数中通过引用接受,这意味着对其进行了修改。 但是分析器发现该代码包含同名的局部变量,该局部变量与功能参数的范围重叠,从而导致更改丢失。

奇怪的指针


V774释放内存后使用了“ cu”指针。 计算器.cc 3595

 MathStructure Calculator::convertToBestUnit(....) { .... CompositeUnit *cu = new CompositeUnit("", "...."); cu->add(....); Unit *u = getBestUnit(cu, false, eo.local_currency_conversion); if(u == cu) { delete cu; // <= return mstruct_new; } delete cu; // <= if(eo.approximation == APPROXIMATION_EXACT && cu->hasApproximateRelationTo(u, true)) { // <= if(!u->isRegistered()) delete u; return mstruct_new; } .... } 

分析器警告释放内存后,该代码包含对cu对象的方法的调用。 但是,如果您试图理解代码,那么它将更加奇怪。 首先, 删除cu的调用总是发生-在此情况下及之后。 其次,条件之后的代码假定指针ucu不相等,因此清除cu对象后,使用u对象是合乎逻辑的。 很有可能在代码中打了一个错字,并且计划仅使用变量u

使用查找功能


V797 '查找'函数的使用就像返回布尔类型一样。 函数的返回值应该与std :: string :: npos进行比较。 单位cc 404

 MathStructure &AliasUnit::convertFromFirstBaseUnit(....) const { if(i_exp != 1) mexp /= i_exp; ParseOptions po; if(isApproximate() && suncertainty.empty() && precision() == -1) { if(sinverse.find(DOT) || svalue.find(DOT)) po.read_precision = READ_PRECISION_WHEN_DECIMALS; else po.read_precision = ALWAYS_READ_PRECISION; } .... } 

尽管代码可以成功编译,但是由于find函数返回许多类型为std :: string :: size_type的代码,因此看起来很可疑。 如果在线上的任意位置找到该点,则条件为true,除非该点在起点处。 这是一个奇怪的测试。 我不确定,但是也许应该将代码重写如下:

 if( sinverse.find(DOT) != std::string::npos || svalue.find(DOT) != std::string::npos) { po.read_precision = READ_PRECISION_WHEN_DECIMALS; } 

潜在的内存泄漏


V701 realloc()可能泄漏:当realloc()分配内存失败时,原始指针“缓冲区”丢失。 考虑将realloc()分配给一个临时指针。 实用程序cc 703

 char *utf8_strdown(const char *str, int l) { #ifdef HAVE_ICU .... outlength = length + 4; buffer = (char*) realloc(buffer, outlength * sizeof(char)); // <= .... #else return NULL; #endif } 

当使用realloc()函数时,建议使用中间缓冲区,因为如果不可能分配内存,则指向旧内存的指针将不可避免地丢失。

结论


Qalculate项目! 在最佳的免费计算器列表中排名第一,同时它包含许多严重的错误。 但是我们还没有看到他的竞争对手。 我们将尝试浏览所有流行的计算器。

至于与Windows世界计算器质量的比较,而Microsoft实用程序看起来更可靠,质量更高。

通过下载PVS-Studio并在您的项目上进行尝试来检查“计算器”。 :-)



如果您想与说英语的读者分享这篇文章,请使用以下链接:Svyatoslav Razmyslov。 跟随计算器的脚步:Qalculate!

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


All Articles