这是与this fascinating question about detecting divide by zero exceptions at compile time相关的问题。
从Eric Lippert的回答中,要实现这一点并非易事(我想这就是为什么它没有提供)。
我的问题是:
无论"级别如何,进行这些类型检查的难度都是相同的。例如语言更高的水平与更低的水平?
具体来说,C#编译器将C#转换为MSIL。作为某种二次通过检查的一部分,这些类型的检查在MSIL级别更容易还是更难?
或者,语言本身是否会产生很小的差异?
阅读Eric的答案中列出的问题,我会假设支票在任何语言中都必须相同?例如,您可以使用许多语言进行跳转,因此需要实现Eric描述的流程检查......?
为了使这个问题具体化,MSIL中的这种检查会比在C#中更容易或更难吗?
答案 0 :(得分:13)
这是一个非常有趣和深刻的问题 - 尽管可能不适合这个网站。
如果我理解这个问题,那么在追求缺陷时进行静态分析时,分析语言的选择会产生什么影响;分析师应该查看IL,还是应该查看源代码?请注意,我已经将这个问题从最初的狭隘关注点扩展到零除缺陷。
答案当然是:这取决于。这两种技术通常用于静态分析行业,各有利弊。这取决于您正在寻找哪些缺陷,您正在使用哪些技术来修剪错误路径,抑制误报并推断缺陷,以及您打算如何向开发人员发现已发现的缺陷。
分析字节码比源代码有一些明显的好处。主要的一个是:如果你有一个用于Java字节码的字节码分析器,你可以通过它运行Scala,而无需编写Scala分析器。如果你有一个MSIL分析器,你可以通过它运行C#或VB或F#,而无需为每种语言编写分析器。
在字节码级别分析代码也有好处。当你有字节码时,分析控制流是非常容易的,因为你可以非常快速地将字节码块组织成"基本块&#34 ;;基本块是代码区域,其中没有指令分支到其中间,并且块的每个正常出口都在其底部。 (例外情况当然可以在任何地方发生。)通过将字节码分解为基本块,我们可以计算相互分支的块图,然后根据其对局部和全局状态的操作来汇总每个块。字节码非常有用,因为它是对代码的抽象,在较低的层次上显示了实际发生的情况。
这当然也是它的主要缺点; 字节码丢失了有关开发者意图的信息。任何需要源代码信息以检测缺陷或防止误报的缺陷检查器在字节码上运行时都会产生不良结果。考虑例如C程序:
#define DOBAR if(foo)bar();
...
if (blah)
DOBAR
else
baz();
如果这个可怕的代码被降低到机器代码或字节码,那么我们所看到的只是一堆分支指令,我们不知道我们应该在这里报告缺陷,{{1}如开发人员所希望的那样绑定else
而不是if(foo)
。
C预处理器的危险性众所周知。但是,在字节码级别对复杂的降低代码进行分析时,也存在很大的困难。例如,考虑像C#:
这样的东西if(blah)
显然async Task Foo(Something x)
{
if (x == null) return;
await x.Bar();
await x.Blah();
}
无法在此处取消引用为null。但是C#会把它降低到一些绝对疯狂的代码;该代码的一部分看起来像这样:
x
等等。 (除此之外它要复杂得多。)然后将其降低为更抽象的字节码;想象一下,试图理解这个代码在交换机级别被降级为gotos和委托降低到闭包。
分析等效字节码的静态分析器完全在其权利范围内说"显然x可以为null,因为我们在交换机的一个分支上检查它;这证明必须在其他分支上检查x的无效性,而不是,因此我将在其他分支上给出空取消引用缺陷"。
但这可能是误报。我们知道静态分析器可能没有的东西,即零状态必须在每个其他状态之前执行,并且当协同程序恢复时x将始终检查为null < / em>的。从原始源代码中可以看出这一点,但很难从字节码中梳理出来。
如果您希望获得字节码分析的好处而没有缺点,那么您做了什么?有各种各样的技术;例如,你可以编写自己的中间语言,它比字节码更高级 - 它具有高级结构,如&#34; yield&#34;或&#34; await&#34;,或&#34; for loop&#34; - 编写一个分析该中间语言的分析器,然后编写编译器,将每种目标语言(C#,Java,等等)编译成您的中间语言。这意味着编写了很多编译器,但只有一个分析器,编写分析器可能很难。
我知道这是一次非常简短的讨论。这是一个复杂的主题。
如果字节码上的静态分析器的设计让您感兴趣,请考虑研究Infer的设计,这是一种用于Java和其他语言的开源静态分析器,它将Java字节码转换为适用于分析堆属性的更低级字节码;首先阅读分离逻辑以推断堆属性。 https://github.com/facebook/infer