我编写了一个实用程序来为Java方法创建CFG(控制流图(,该方法的节点是基本块而不是指令。
我不能将异常抛出视为 CFG 中的边缘。原因是:
try 块- 中的每个指令都可能引发异常/错误,这些异常/错误可以由任何嵌套的 try-catch 块处理。如果我们将异常抛出视为边缘,则要处理的路径数量会急剧增加,CFG 中的节点数量也会急剧增加。
- 我们需要知道异常的继承层次结构,然后才能决定哪些跳转是可能的。
静态代码分析器如何解决这个问题?
我被困在这一点上。如果我必须继续,我应该怎么做?
编辑:就我而言,我可以将支持限制为那些可以指定抛出异常的位置和哪些用例。这解决了我的第二个问题。我仍然想知道通用静态代码分析器如何管理这一点。
以下是我在 Krakatau 反编译器中处理该问题的方式:
我们需要先知道异常的继承层次结构,然后才能知道 决定哪些跳跃是可能的。
Krakatau 要求任何引用类的类定义都可用,因此它知道继承层次结构。但是,如果我重新做一遍,我就不会这样做。要求类定义会使反编译器难以操作,因为查找和添加依赖项是一个巨大的痛苦。如果您同意分析不太精确,则实际上不需要它。相反,您可以假设所有异常都可以到达所有处理程序。在实践中,我希望它会导致几乎相同的结果。
try 块中的每个指令都可能引发异常/任何嵌套 try-catch 块都可以处理的错误。如果 我们将异常抛出视为边缘,即要处理的路径数 急剧增加,CFG 中的节点数量也会急剧增加。
Krakatau 确实在 CFG 中包含异常作为边,这会导致您发现的问题。为了减少边缘的数量,我假装只有某些指令可以抛出(方法调用、数组访问、除法等(。这在技术上不正确,但它为现实世界的代码做了正确的事情。我从未见过任何真正关心链接错误、Thread.Stop 等引发异常的东西。不过,我后来确实添加了一个选项来禁用此行为。
无论如何,这对于大多数代码来说已经足够好了,但它有时会导致性能问题。特别是,具有大量字段访问或方法调用的非常大的方法会导致巨大的 CFG,从而使反编译非常慢。我尝试了一些技巧来优化这一点,但最终,解决方案是从基本块转移到扩展的基本块。
扩展基本块类似于基本块,不同之处在于异常边缘以半隐式方式表示,从而产生更小的 CFG。EBB 由直线代码组成,除了异常边缘外,中间没有入口点或出口点,并且块中的每个指令都由同一组异常处理程序覆盖。这样,您就不必每条指令都有一个异常边缘,而是每个块都有一个异常边缘,从而使事情变得更加高效。
即使是具有数千个方法调用的 Java 方法,通常也只有几个 try/catch,因此只有几个 EBB。