作者——SergeyVasiliev
Microsoft发布的项目源是进行分析的一个很好的理由。这一次也不例外,今天我们将查看代码中的可疑部分。

简要介绍项目和分析程序
是由微软专家开发的机器学习系统。项目源代码最近已经在GitHub上可运行了()。你可以在此处找到有关该项目的更多详细信息()。
该项目由静态代码分析器检查。需要提醒的是,PVS-Studio正在Windows,Linux,macOS下检查C\C++\C#(以及Java)代码中的错误。到目前为止,C#代码仅在Windows下分析。你可以下载并尝试在你的项目中使用()。
顺便说一句,这不是微软的第一个项目,使用PVS-Studio进行检查——还有其他项目:Roslyn(),MSBuild(),PowerShell(),CoreFX()等。
注意。如果你或你的朋友对Java代码分析感兴趣,可以选择"我想要分析Java"来写信给我们()。
接下来,让我们看看代码中的问题。
它是一个Bug还是一个特性?
我建议你自己发现错误——这是一个完全可能的任务。我保证不会出现与"2017年C++项目中的十大错误"()一文中相同的内容。因此,请花点时间阅读代码片段后给出的分析器警告。


PVS-Studio警告:V3001''运算符的左侧和右侧有相同的子表达式'()'。运行时()
从源代码片段中可以看出,该方法使用了几个变量——transition1和transition2。类似名称的使用有时是合理的,但值得记住的是,在这种情况下,在某个地方意外犯错的可能性会增加。
所以在检查无穷大时的数字时会发生这种情况()。由于误差,检查了一个相同的变量的值两次。第二个子表达式中的变量必须成为一个选中的值。
另一个类似的可疑代码。

PVS-Studio警告:V3001"|"运算符的左侧和右侧有相同的子表达式""。编译器()
当形成bf变量值时,枚举数被使用了两次。这段代码要么包含一个redudant标志设置操作,要么是第二次使用,另一个枚举数必须在这里进行。
顺便说一句,这段代码是在源代码的一行中编写的。在我看来,如果它以表格形式格式化,则更容易找到问题。
让我们继续。我引用了整个方法体,并建议你自己再次发现错误。



找到了?我们来检查一下!
PVS-Studio警告:
V3003检测到'if(A){}elseif(A){}'模式的使用。存在逻辑错误的可能性。检查行:1719,1727。编译器()
V3003检测到'if(A){}elseif(A){}'模式的使用。存在逻辑错误的可能性。检查行:1721,1729。编译器()
让我们简化代码,以便问题变得更加明显。

几个if语句的条件表达式和then分支是重复的。也许,这段代码是用复制粘贴方法编写的,这导致了一个问题。现在事实证明,重复的分支永远不会被执行,因为:
如果条件表达式为true,则从相应的对执行第一个if语句的主体;
如果条件表达式在第一种情况下为假,则在第二种情况下它也将为假。
由于当时的分支包含相同的操作,现在它看起来像冗余代码,这是令人困惑的。也许,这里存在一种不同的问题——必须运行其他检查而不是重复检查。
我们继续吧。


PVS-Studio警告:
V3004'then'语句相当于'else'语句。运行时()
V3004'then'语句相当于'else'语句。运行时()
代码看起来非常可疑,因为它包含两个条件语句,其中then和else-branches具有相同主体。可能在这两种情况下,都值得返回不同的值。另一方面,如果它是构思好的行为,那么删除冗余条件语句将是有用的。
我遇到了一些更有趣的循环。示例如下:

PVS-Studio警告:V3020循环内的无条件"中断"。编译器()
由于无条件break语句的原因,只执行了一次循环迭代,甚至不使用已更改的控制变量。一般来说,代码看起来很奇怪和可疑。
相同的方法(精确复制)发生在另一个类中。相应的分析器警告:V3020循环内的无条件"中断"。()
顺便说一下,我在一个循环中偶然发现了一个无条件的继续语句(分析器通过相同的诊断发现它),但有人说它是一个特殊的临时解决方案:

让我提醒你,无条件break语句旁边没有这样的注释。
让我们继续。


PVS-Studio警告:V3022表达式'resultIndex==null'始终为false。编译器()
我立即注意到声明和给定检查之间,resultIndex变量的值可能会改变。但是,在检查resultIndex!=null和resultIndex==null之间,值无法更改。因此,表达式resultIndex==null的结果将始终为false,永远不会生成异常。
我希望你有兴趣独立搜索bug,即使我没有找到问题的建议,但为了以防万一,我建议你再次这样做。方法代码很小,我将完全引用它。



让我们看看这里发生了什么。输入字符串由字符"|"解析。如果数组的长度与预期的长度不匹配,则必须生成异常。等一下?由于没有适合表达式([.1)和(3..))所需的两个值范围的数字,因此表达式的结果将始终为false。因此,此检查可以防止任何操作,并且不会引发异常。
这就是分析器阻止我们的原因:V3022Expression''总是的false。可能应该在这里使用'||'运算符。
我遇到了一个可疑的代码操作。

PVS-Studio警告:V3041表达式从'int'类型隐式转换为'double'类型。考虑使用显式类型转换来避免丢失小数部分。一个例子:doubleA=(double)(X)/Y;。()
这里有可疑的地方:执行整数除法(variablesaverageDocLength和numUniqueTopicsPerDoc属于int类型),但结果写在double类型的变量中。这引出了一个问题:它是故意制造出来的,还是真正意义上的实数划分?如果变量expectedRepeatOfTopicInDoc属于int类型,这将禁止可能的问题。
在其他地方,使用方法,其参数是可疑变量expectedRepeatOfTopicInDoc,例如,如下所述。

averageWordsPerTopic属于int类型,在其使用位置被强制转换为double。
这是另一个使用地点:

请注意,变量具有与原始示例中相同的名称,仅用于expectRepeatOfWordInTopic初始化实数的分割(由于显式numDocs强制转换为double类型)。

PVS-Studio警告:V3041表达式从'int'类型隐式转换为'double'类型。考虑使用显式类型转换来避免丢失小数部分。一个例子:doubleA=(double)(X)/Y;。运行时()
分析器再次发现了一个整数除法的可疑操作,因为2和3是整数数字文字,表达式2/3的结果将为0。结果,表达式如下所示:

你必须承认,这有点奇怪。有几次我又回到这个警告,试图找到一个技巧,而不是试图将其添加到文章中。这个方法充满了数学和公式(坦白地说,拆解这些方法并不具有吸引力),这里有很多值得期待的东西。此外,我尽可能地对警告持怀疑态度,我在文章中加入了警告,并描述了我对它们的初步深入研究。
然后我明白了——为什么你需要这样一个乘数0,写成2/3?因此,无论如何,这个地方值得一看。

PVS-Studio警告:V3080可能无效撤销引用。考虑检查"价值"。编译器()
基于条件的相当公平的分析器警告。表达式(defaultValue)中可能出现无效撤销引用,ifvalue==null。由于此表达式是运算符||的右操作数,因此对于它的求值,左操作数必须具有false值,并且为此目的,至少有一个变量defaultValue\value不等于null。最后,如果defaultValue!=null,并且value==null:
defaultValue==null-false;
defaultValue==nullvalue==null-false;(没有执行价值检查)
(defaultValue)-NullReferenceException,作为值-null。
让我们看看另一个示例:

PVS-Studio警告:V3080可能的无效撤销引用。考虑检查'traitFeatureWeightDistribution'。推荐()
让我们省略额外的字符串,只留下评估布尔值的逻辑,以便更容易理清:

同样,运算符||的右操作数仅在评估左边的结果为false时才进行评估。左操作数可以取false值,包括traitFeatureWeightDistribution==null和ofbiasFeatureWeightDistribution!=null。然后是运算符||的右操作数将被评估,并调用,所有这些操作将导致抛出ArgumentNullException。
另一段有趣的代码:

PVS-Studio警告:V3095在对null进行验证之前使用过'quantiles'对象。检查线:91,92。运行时()
请注意,访问属性,然后检查分位数是否为null。最后,如果quantiles==null,该方法将抛出异常,但是错误的异常会被抛出到错误的位置。可能是线条倒置了。
如果你已成功应对前面列出的错误,我建议你喝一杯咖啡并尝试在下面的方法中发现错误。为了使它更有趣,我将完全引用方法代码。
(full-size:)

好吧,好吧,这是一个玩笑。让我们简化任务:

分析仪对此代码发出以下警告:V3008'lowerBound'变量被连续赋值两次。也许这是一个错误。检查行:324,323。运行时()
实际上,在最后一个else分支中,下界(lowerBound)变量的值被连续分配两次。显然(从上面的代码判断),上界(theupperBound)变量应该参与其中一个赋值。
让我们继续。

PVS-Studio警告:V3081'r'计数器未在嵌套循环中使用。考虑检查"c"计数器的使用情况。()
请注意,内循环计数器-r未在此循环的主体中使用。因此,事实证明,在内部循环的所有迭代中,执行具有相同元素的相同操作——在索引中,还使用外部循环的计数器©,而不是内部循环(r)中的一个。
让我们看看其他有趣的问题。

PVS-Studio警告:V3117未使用构造函数参数"useLazyQuantifier"。运行时()
在构造函数中没有使用参数useLazyQuantifier。它看起来特别可疑,因为在类中,属性是用适当的名称和类型定义的——UseLazyQuantifier。显然,人们忘了通过相应的参数进行初始化。
我还遇到了几个有潜在危险的事件处理程序。其中一个示例如下:

PVS-Studio警告:V3083事件'Started'的不安全调用,NullReferenceException是可能的。在调用之前,请考虑将事件分配给局部变量。()
事实上,在检查null不等式和处理程序调用之间,如果在测试null和调用事件处理程序之间,事件没有订阅者,将抛出NullReferenceException异常,则会发生事件取消订阅。例如,要避免此类问题,可以将对委托链的引用保留到局部变量中,或使用"?"运算符来调用处理程序。
除上述代码片段外,还发现了其他35个这样的地方。
顺便说一句,发生了785个V3024警告。在使用运算符'!='或'=='比较实数时会发出V3024警告。我不会详述为什么这种比较并不总是正确的。有关这方面的更多信息,请参阅文档,还有一个指向StackOverflow的链接。()
考虑到经常满足公式和计算的事实,这些警告即使被置于第3级也很重要(因为它们几乎与所有项目无关)。
如果你确定这些警告"无关紧要",只需单击一下即可将其删除(),从而减少分析器触发的总次数。

结论
我希望你从这篇文章中学到了一些新的东西,或者至少有兴趣阅读它。
我希望开发人员能够快速解决问题所在的地方,我想提醒一下,犯错是可以的,因为我们是人。但这也是为什么我们需要额外的工具,如静态分析工具来找到一个人忘记/做错的东西,对吧?无论如何,祝你的项目好运!
此外,请记住,静态分析器的最大用量是在正常使用时获得的。






