我们如何解释静态代码分析中的异常抛出?

How do we account for exception throws in static code analysis?

我编写了一个实用程序来为 java 方法创建 CFG(控制流图),其节点是基本块而不是指令。

我无法将异常抛出视为 CFG 中的边缘。原因是:

  1. try 块中的每条指令都可能引发异常/错误,这些异常/错误可以由任何嵌套的 try-catch 块处理。如果我们将异常抛出视为边缘,则处理路径的数量会急剧增加,CFG 中的节点数量也会急剧增加。
  2. 我们需要先了解异常的继承层次结构,然后才能决定哪些跳转是可能的。

静态代码分析器如何解决这个问题?

我卡在这一点上了。如果我必须继续,我应该怎么做?

编辑:在我的例子中,我可以限制对那些可以指定在何处和哪些异常被抛出的用例的支持。这解决了我的第二个问题。我仍然想知道通用静态代码分析器如何管理它。

以下是我在 Krakatau decompiler 中处理问题的方式:

We need to know the inheritance hierarchy for exceptions before we can decide what jumps are possible.

Krakatau 要求任何引用的 classes 的 class 定义可用,因此它知道继承层次结构。但是,如果我重新来过,我不会这样做。需要 class 定义使得用户难以操作反编译器,因为查找和添加依赖项是一个巨大的痛苦。如果您认为分析不太精确,那么您实际上并不需要这个。您可以改为假设所有异常都可以到达所有处理程序。在实践中,我预计它会导致几乎相同的结果。

Every instruction in try block can potentially throw exceptions / errors which can be handled by any of the nesting try-catch blocks. If we consider exception throws as edges, the number of paths to process increases drastically, and so will the number of nodes in CFG.

Krakatau 确实在 CFG 中包含异常作为边,这会导致您发现的问题。为了减少边数,我假装只有某些指令可以抛出(方法调用、数组访问、除法等)。这在技术上是不正确的,但它对现实世界的代码做了正确的事情。我从来没有见过任何真正关心从 link 错误、Thread.Stop 或类似错误中抛出异常的东西。不过我后来确实添加了一个选项来禁用此行为。

无论如何,这对大多数代码来说已经足够好了,但有时会导致性能问题。特别是,具有大量字段访问或方法调用的非常大的方法会导致巨大的 CFG,从而使反编译非常缓慢。我尝试了一些技巧来优化它,但最终,解决方案是从基本块移动到扩展基本块。

Extended Basic Blocks 与 Basic Blocks 类似,除了异常边缘是半隐式表示的,从而导致 CFG 小得多。 EBB 由直线代码组成,除了异常边缘外,中间没有入口点或出口点,块中的每条指令都由同一组异常处理程序覆盖。这样一来,就不是每条指令都有一个异常边缘,而是每个块都有一个异常边缘,从而使事情变得更加高效。

即使 Java 具有数千个方法调用的方法通常也只有几个 try/catches,因此只有几个 EBB。