以前,我们对大型数学程序包(例如Scilab和Octave)进行代码审查,因此,计算器仍然作为小型实用程序而与众不同,在这些实用程序中,由于其较小的代码库而很难出错。 我们没有注意他们是错误的。 发布Windows计算器源代码的案例表明,实际上每个人都对讨论隐藏在其中的错误类型感兴趣。 此外,错误的数量足以撰写有关此内容的文章。 我和我的同事们决定探索许多流行的计算器的代码,结果发现Windows计算器的代码还不错(破坏器)。
引言
夸口! 是一款多功能的跨平台桌面计算器。 它使用简单,但具有通常为复杂的数学软件包保留的功能和多功能性,以及满足日常需求(例如货币换算和百分比计算)的有用工具。 该项目包含两个组件:
libqalculate (库和CLI)和
qalculate-gtk (GTK + UI)。 该研究仅涉及libqalculate代码。
为了轻松地将项目与我们最近检查过的Windows计算器进行比较,我引用了libqalculate的Cloc实用程序的输出:
从主观上考虑,它存在更多错误,并且比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); } .... }
if和
else块中的代码绝对相同
。 相邻的代码片段与此非常相似,但是其中使用了不同的功能:
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; .... }
在这种情况下,由于在一个地方写入了
mtr2而不是
mtr ,所以出现了重复的表达式
。 因此,该条件中不存在对
mtr.number()。IsReal()函数的调用。
V501在“ ||”的左侧和右侧有相同的子表达式“ vargs [1] .representsNonPositive()”。 操作员。 BuiltinFunctions.cc 5785
我们永远不会手动发现此代码中的缺陷! 但是这里有。 此外,在原始文件中,这些片段用一行写成。 分析器检测到重复的表达式
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; .... }
3年前,当我注意到此类代码后,我为我和其他开发人员写了一个备忘单:“
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();
在一个函数中,无论是否进行检查,都将
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];
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;
分析器警告说,代码在释放内存后立即调用
cu对象的方法。 但是当试图解决它时,代码却变得更加奇怪。 首先,调用
delete cu总是会发生-在此情况下及之后都发生。 其次,该条件之后的代码表示指针
u和
cu不相等,这意味着在删除
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));
当使用
realloc()函数时,建议使用中间缓冲区,以防万一无法分配内存,指向旧内存区域的指针将不可避免地丢失。
结论
Qalculate! 项目在最佳免费计算器的列表中名列前茅,而其中包含许多严重错误。 另一方面,我们尚未检查其竞争对手。 我们将尝试遍历所有流行的计算器。
至于与Windows世界中计算器的质量进行比较,到目前为止,Microsoft提供的实用程序看起来更加可靠且运行良好。
检查您自己的“计算器”-下载
PVS-Studio并为您的项目尝试。 :-)