与JSE JavaDoc相比,可以存在哪些类层次结构差异?

我目前正在maven post编译任务中生成一些ASM代码。 在Java 6中,引入了StackMapTable来表示堆栈中的数据类型,这在以后的版本中是必需的。 所以我自动确定可以在堆栈上的最具体的类。 现在我遇到了问题,在我的VM中,ThaiBuddhistDate和HijrahDateinheritance了ChronoLocalDateImpl,因此它会在StackMapTable中创建这种类型,这显然会在其他VM(甚至是版本)中崩溃。 所以我想,也许我应该将计算更改为最小强制,这可能会导致(理论上)类和接口的类似问题。 现在我正在尝试为我的问题找到解决方案,所以我必须弄清楚,可能会出现哪些差异。

附加类是否只能在inheritance层次结构中的任何位置出现? 假设JavaDoc具有inheritance层次结构,如:

对象 – Foo – Bar – FooBar

我可以在所有的inheritance结构中有其他类吗?

对象 – Baz – Foo – Bar – FooBar

对象 – Foo – Baz – Bar – FooBar

对象 – Foo – Bar – Baz – FooBar

类似于接口:接口是否也可以从其他接口inheritance,这些接口未在文档中定义,或者“仅”类可以具有额外的独立接口或接口,这些接口或接口基于已定义的接口或接口,甚至不是任何接口或接口?

看来你正在使用COMPUTE_FRAMES选项,这将导致ASM库通过getCommonSuperClass合并通过可能的代码路径遇到的类型,类似于旧的validation程序所做的,并且稍微颠覆了堆栈映射表的概念。

正如您已经指出的那样,ASM的getCommonSuperClass实现可能返回一个实际上不可访问的类型,如JRE内部基类,并忽略接口关系。 更大的问题是您无法使用此方法的不同实现来解决此问题,因为传递给此方法的信息不足以确定正确的类型。

正确的类型是随后需要的类型,当然,它也应该与通过所有可能的代码路径提供的内容兼容,这是validation者将要/应该检查的内容。 如果您的代码生成器的设计方式使其生成有效代码,则指定后续所需类型应足以创建有效的堆栈映射表条目,但传递给getCommonSuperClass的传入类型不足以告诉您需要什么类型。

要说明此问题,请考虑以下示例类

 class Example { public static CharSequence problematicMethod() { return Math.random()>0.5? new StringBuilder("x"): new StringBuffer("y"); } } 

以及下面的代码分析编译的(例如通过javac )类以及ASM在被告知从头开始重新计算堆栈映射帧时默认生成的内容:

 static void printFrame(int nLocal, Object[] local, int nStack, Object[] stack) { StringBuilder sb = decode(new StringBuilder().append("Locals: "), local, nLocal); System.out.println(decode(sb.append(", Stack: "), stack, nStack)); } private static StringBuilder decode(StringBuilder sb, Object[] array, int num) { if(num==0) return sb.append("[]"); sb.append('['); for(int ix = 0; ix"); else if(o==Opcodes.INTEGER) sb.append("int"); else if(o==Opcodes.FLOAT) sb.append("float"); else if(o==Opcodes.DOUBLE) sb.append("double"); else if(o==Opcodes.LONG) sb.append("long"); else if(o==Opcodes.NULL) sb.append("null"); else if(o==Opcodes.TOP) sb.append("-"); else sb.append(Type.getObjectType(o.toString()).getClassName()); sb.append(","); } sb.setCharAt(sb.length()-1, ']'); return sb; } public static void main(String[] args) throws IOException { final MethodVisitor printFramesMV = new MethodVisitor(Opcodes.ASM5) { @Override public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { printFrame(nLocal, local, nStack, stack); } }; final ClassVisitor printFrames = new ClassVisitor(Opcodes.ASM5) { @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { return name.equals("problematicMethod")? printFramesMV: null; } }; ClassReader cr = new ClassReader(Example.class.getName()); System.out.println("##original"); cr.accept(printFrames, ClassReader.EXPAND_FRAMES); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); cr.accept(cw, ClassReader.SKIP_FRAMES); System.out.println("##from ASM"); new ClassReader(cw.toByteArray()).accept(printFrames, ClassReader.EXPAND_FRAMES); } 

这将打印出来

 ##original Locals: [], Stack: [] Locals: [], Stack: [java.lang.CharSequence] ##from ASM Locals: [], Stack: [] Locals: [], Stack: [java.lang.AbstractStringBuilder] 

这显示了您在问题中解释的相同问题,ASM将生成一个引用特定于实现的类的框架。 javac生成的代码引用了所需的类型,该类型与方法的返回类型兼容。 您可以在getCommonSuperClass学习StringBuilderStringBuffer并找出它们都实现CharSequence ,但这不足以理解CharSequence在这里是正确的类型,因为我们可以简单地将示例更改为

 class Example { public static Appendable problematicMethod() { return Math.random()>0.5? new StringBuilder("x"): new StringBuffer("y"); } } 

得到

 ##original Locals: [], Stack: [] Locals: [], Stack: [java.lang.Appendable] ##from ASM Locals: [], Stack: [] Locals: [], Stack: [java.lang.AbstractStringBuilder] 

由于传入的类实现了两个接口,因此只需查看传入的StringBuilderStringBuffer类型,就无法确定CharSequenceAppendable是否是正确的合并类型。

要进一步评估此问题,请查看

 class Example { public static Comparable problematicMethod() { return Math.random()>0.5? BigInteger.valueOf(123): Double.valueOf(1.23); } } 

哪个产生

 ##original Locals: [], Stack: [] Locals: [], Stack: [java.lang.Comparable] ##from ASM Locals: [], Stack: [] Locals: [], Stack: [java.lang.Number] 

这里,ASM的结果是一个public类型,但是这个公共基类没有实现所需的Comparable ,所以这个代码实际上已经被破坏了。


对于使用ASM的COMPUTE_FRAMES选项的所有代码生成器来说,这是一个好运,HotSpot的validation器对接口类型有很大的容忍度,换句话说,它根本不validation赋值的正确性(这包括方法调用的接收者)当两种类型中的至少一种是界面时。

如果你想生成代码,即使对于接口严格执行它们的validation器,你也不应该使用该选项并开始自己生成堆栈映射帧,方法是不使用COMPUTE_FRAMES选项并发出正确的visitFrame调用(或插入适当的节点,如果您正在使用树API)。

似乎普遍担心这样做,但并不复杂。 如前所述,它基本上意味着说明您的代码生成器已经知道的内容。 它实际上并不是试图找到一个常见的类型,它是关于指定你将使用的内容以及你的代码生成器是否正确,已经是它,但如果没有,ASM的计算也无法修复代码。

为了保持您的具体示例,在处理ThaiBuddhistDateHijrahDate您已经知道在分支合并点(我想)之后您将它们作为ChronoLocalDate处理,而ASM最终处于特定于实现的非public类型,但是如果该类型不存在,ASM只使用java.lang.Object因为它不考虑接口。 如果ASM考虑了接口,它必须在ChronoLocalDateSerializable之间做出决定,既不比另一个更具体。 这种设计根本无法解决。

为了进一步说明,“合并传入类型”和“将使用什么”之间的结果有多么不同,请看

 class Example { public static void problematicMethod() { if(Math.random()>0.5) { java.awt.ScrollPane b = new java.awt.ScrollPane(); } else { javax.swing.JTabbedPane t = new javax.swing.JTabbedPane(); } } } 
 ##original Locals: [], Stack: [] Locals: [], Stack: [] ##from ASM Locals: [], Stack: [] Locals: [java.awt.Container], Stack: [] 

在这里,ASM浪费资源来找出深层次结构树中的公共基类,而只是声明“删除变量”就足够了……