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