使用ASM(5.x)在字节代码中检测运行时的递归方法调用:howto?

时间:2015-01-27 14:24:42

标签: java bytecode java-bytecode-asm

问题如下; Java代码中的方法是:

Rule foo()
{
    return sequence(foo(), x());
}

这将引发解析循环,当然应该避免;但是,是合法的:

Rule foo()
{
    return sequence(x(), foo());
}

现在,代码中的其他地方我可以访问RuleMethod,这是一个扩展MethodNode的类,因此我可以访问以下信息:

  • ruleMethod.namefoo; (在MethodNode中定义)
  • ruleMethod.desc()Lorg/parboiled/Rule;(在MethodNode中定义)
  • ruleMethod.ownerClasscom.github.fge.grappa.experiments.SelfReferringRule.MyParser(在RuleMethod
  • 中定义)

上面第一个代码提取的字节码如下:

Method 'foo':
 0    L0
 1     ALOAD 0
 2     ALOAD 0
 3     INVOKEVIRTUAL com/github/fge/grappa/experiments/SelfReferringRule$MyParser.foo ()Lorg/parboiled/Rule;
 4     ALOAD 0
 5     INVOKEVIRTUAL com/github/fge/grappa/experiments/SelfReferringRule$MyParser.x ()Lorg/parboiled/Rule;
 6     ICONST_0
 7     ANEWARRAY java/lang/Object
 8     INVOKEVIRTUAL com/github/fge/grappa/experiments/SelfReferringRule$MyParser.sequence (Ljava/lang/Object;Ljava/lang/Object;[Ljava/lang/Object;)Lorg/parboiled/Rule;
 9     ARETURN
10    L1

这意味着我可以获得的每一条信息,至少在上面的字节码中,foo() } <{1>} sequence()的第一个参数调用,因为构造函数接受三个参数,并且堆栈上有三个元素。

但我当然不能视察&#34;在运行时。因此我需要一种方法来做到这一点......

看起来我需要的是MethodVisitor并且有点visitInsn(),然后看看有哪些参数并进行适当检测......

但我不知道从哪里开始;在网上搜索似乎只提供了如何修改字节代码的示例,而不是检测这种情况:/

我从哪里开始?

3 个答案:

答案 0 :(得分:2)

使用tree api分析通常会容易得多,因为它可以让您轻松回溯并为流量分析提供支持。

如果我正确理解您的问题,您需要做的就是(如果您希望支持的只是简单的情况,例如您的示例)从调用序列向后扫描。如你所知,代码编译堆栈上的内容必须是有效的,所以只需算上三个方法调用/ field gets / etc.

如果您想支持更复杂的场景,其中输入通过分支语句分配给变量,则需要进行某种流分析。

答案 1 :(得分:0)

创建MethodVistor,当您在方法foo()的{​​{3}}内时,查找visitCode()以及name中的visitMethodInsn()参数是foo你知道你有一个递归调用方法。

在您的字节码列表中,您有三条INVOKEVIRTUAL条指令,这些指令由visitMethodInsn()函数按顺序访问。如果要检查序列,可以跟踪方法调用的顺序。您会先看到foo(),然后是x(),最后是sequence()

3     INVOKEVIRTUAL com/github/fge/grappa/experiments/SelfReferringRule$MyParser.foo ()Lorg/parboiled/Rule;
4     ALOAD 0
5     INVOKEVIRTUAL com/github/fge/grappa/experiments/SelfReferringRule$MyParser.x ()Lorg/parboiled/Rule;
6     ICONST_0
7     ANEWARRAY java/lang/Object
8     INVOKEVIRTUAL com/github/fge/grappa/experiments/SelfReferringRule$MyParser.sequence (Ljava/lang/Object;Ljava/lang/Object;[Ljava/lang/Object;)Lorg/parboiled/Rule;

答案 2 :(得分:0)

如果我的意图正确,你想要检测直接左递归,这是一个无法处理的。但是,可以处理左递归:

本质上,解析器必须检测左递归并在那里失败,但请记住发生左递归。当以递归方式使用的规则成功时,结果将保存为“种子”,并且解析过程将在原始输入位置重新启动。这次,当左递归发生时,使用种子而不是失败。这个过程重复进行。

有关说明,请参阅http://www.vpri.org/pdf/tr2007002_packrat.pdf。该算法可以很容易地适应PEG解析器。

类似于使用此技术进行parboiled的解析库是https://github.com/ruediste/lambda-peg-parser