【问题标题】:Is it possible to use Rosyln or Resharper to detect possible DivideByZero cases?是否可以使用 Rosyln 或 Resharper 来检测可能的 DivideByZero 案例?
【发布时间】:2018-01-08 21:11:27
【问题描述】:

我正在尝试确定是否有一种编程方式来检查我的代码库中可能存在的DivideByZeroException。我的代码库包括一系列相对简单到相对复杂的公式,其中大约 1500 个(并且还在增长)。编写新公式时,必须注意确保安全地进行除法,以避免在处理这些公式时出现异常。

例如

decimal val1 = 1.1m;
decimal val2 = 0m;
var res = val1/val2; //bad

var res = val2 == 0 ? 0 : val1/val2; //good

是否有任何方法可以使用 Roslyn 或 Resharper 或其他工具来检查我的代码库并确定 DivideByZeroException 没有得到适当防范的情况?这些公式基于足够动态和复杂的数据,使得使用简单的单元测试很难检测到这一点。这些公式可以访问数百个输入,并且可以动态地相互构建。

我的环境是:VS2017Pro,Resharper(最新)。

【问题讨论】:

  • 回答您唯一的问题:是的,有可能。您需要解析每个 / 并检查是否有 if (x == 0) 用于划分 x 变量
  • 请注意,在您的情况下,无论如何您都不会得到 DivideByZeroException - 您会得到正无穷大,因为您正在执行浮点除法。
  • @JonSkeet 谢谢。我的代码通常使用小数,这会引发异常。我已经相应地更新了示例。

标签: c# resharper roslyn roslyn-code-analysis


【解决方案1】:

首先:以 100% 的准确度静态检测除以零的结果——这样您既不会报告可能的缺陷,也不会报告不可能的缺陷——是不可能的。它相当于已知无法解决的停机问题。

因此,我们必须决定是否在过度近似、有时会出现误报的情况下犯错,或者是在近似不足、有时会出现误报的情况下犯错。这会对工具的设计及其可用性特性和性能产生重大影响。

正如另一个答案所述,一个简单的启发式方法是将不在等式测试的结果子节点上的所有分区标记为零。这将有一个巨大的误报率。例如:

var res = val2 == 0 ? 0 : val1 / val2;

var res = val2 != 0 ? val1 / val2 : 0;

这些可能会被正确标记为否定。但是呢

int? res = val2 > 10 ? (int?) (val1 / val2) : null;

那里不可能被零除。但提议的测试无法捕捉到它,并且会将这些错误归类为阳性。

这样的事情呢?

int i1 = whatever;
int i2 = whatever;
int i3 = whatever;
int i4 = i1 > 0 && i2 > 0 ? i3 / (i1 + i2) : 0;

首先:我们可以假设总和永远不会溢出到零吗? 在设计检查器时这是一个非常重要的问题。通常我们会做出保守的假设,即这些值足够小而不会溢出。但是现在我们有另一个问题:您的程序是否足够聪明,可以理解两个正整数之和永远不会为零?

为了静态表示这些类型的计算,您可能必须构建一个带有算术模型的 SMT 求解器。

您还需要一个流量检查器:

int i1 = whatever;
int i2 = whatever;
if (i2 == 0) return;
int i3 = i1 / i2;

这不能除以零,因为如果是,我们已经返回。您必须进行流分析,以跟踪不同分支上各种表达式的零度。请记住,C# 中的流分析可能非常奇怪:

int i1 = whatever;
int i2 = whatever;
if (i2 != 0)
  goto X;
try {
  Debug.Assert(i2 == 0);
  goto X;
}
finally {
  throw something;
}
X:
int i3 = i1 / i2;

这段代码真的很奇怪和愚蠢,但它不包含除以零错误,即使我们在可达代码路径上将零分配给 i2,并且有一个可达标签的可达 goto,然后除以 i2。因此,您不应该在这里报告除以零错误!

那些是简单的。现在考虑更复杂的场景:

static int Mean(IEnumerable<int> items) => 
  items.Any() ? items.Sum() / items.Count() : 0;

此代码没有除以零错误。您的缺陷检查器会将其标记为有一个吗?

为了防止这种误报,您需要一个错误路径检测器,它可以理解序列的代数属性:Any() 是确保 Count() 大于零的谓词,并且以此类推。

这将是很多工作,但是您将学到很多关于静态分析的知识!

【讨论】:

  • 在代码items.Any() ? items.Sum() / items.Count() : 0;我认为除以零错误实际上是可能的,如果序列items每次都返回不同的结果被枚举(items.Any()返回true但items.Count()返回0).
  • @LucaCremonesi:是的,这将是您永远不想报告的缺陷的一个很好的例子。想象一下,如果您是开发人员,并且给您一千个缺陷报告进行调查,其中一半是“如果每次都以不同的方式枚举此序列,那么可能会出现问题”。如果我被派去负责,我会很快了解到这是一种用于产生浪费时间的无意义的忙碌工作的工具,并且强烈建议停止使用它。静态分析供应商必须始终避免这种糟糕的误报。
  • @LucaCremonesi:这与您总是假设整数运算不会溢出的原因相同除非您专门编写溢出检测器。如果算术溢出,那么您的代码中已经存在比 div 为零更糟糕的问题;它可能会产生不正确的结果大部分时间不会崩溃。如果算术没有溢出,则基于它溢出的缺陷是误报。所以无论哪种方式,我们都应该假设算术是正确的,并且两个正数之和是正数。
【解决方案2】:

您可以创建一个检查所有DivideExpression 的分析器,如果它不是带有NumericLiteralExpressionEqualsExpression 的子节点,则分析器应为此添加一个应用代码修复的诊断。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-17
    • 1970-01-01
    • 1970-01-01
    • 2010-11-04
    • 2019-02-07
    • 1970-01-01
    相关资源
    最近更新 更多