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

Spi*_*lle 1 java jvm specifications hierarchy

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

附加类只能出现在继承层次结构中的任何地方吗?假设 JavaDoc 具有如下继承层次结构:

对象 - Foo - 栏 - FooBar

我可以在继承结构中随处添加额外的类吗?

对象 - Baz - Foo - Bar - FooBar

对象 - Foo - Baz - 酒吧 - FooBar

对象 - Foo - 酒吧 - Baz - FooBar

类似地,对于接口:接口是否也可以从文档中未定义的其他接口继承,或者类是否可以“仅”具有附加的独立接口或基于定义的接口或什至没有接口?

Hol*_*ger 5

看来您正在使用的COMPUTE_FRAMES选项将导致 ASM 库合并通过可能的代码路径遇到的类型,getCommonSuperClass类似于旧验证器所做的事情,并且在某种程度上歪曲了堆栈映射表的概念。

\n\n

正如您已经注意到的,ASM\xe2\x80\x99s 的实现getCommonSuperClass可能会返回实际上不可访问的类型,如 JRE 内部基类,并忽略接口关系。更大的问题是,您无法使用此方法的不同实现来修复此问题,因为传递给此方法的信息不足以确定正确的类型。

\n\n

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

\n\n

为了说明这个问题,请考虑以下示例类

\n\n
class Example {\n    public static CharSequence problematicMethod() {\n        return Math.random()>0.5? new StringBuilder("x"): new StringBuffer("y");\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

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

这将打印

\n\n
class Example {\n    public static CharSequence problematicMethod() {\n        return Math.random()>0.5? new StringBuilder("x"): new StringBuffer("y");\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

这显示了您在问题中解释的相同问题,ASM 将生成一个引用特定于实现的类的框架。生成的代码引用javac所需类型,该类型与 method\xe2\x80\x99s 返回类型兼容。您可以研究StringBuilderStringBuffergetCommonSuperClass发现两者都实现了CharSequence,但这不足以理解这CharSequence是正确的类型,因为我们可以简单地将示例更改为

\n\n
class Example {\n    public static Appendable problematicMethod() {\n        return Math.random()>0.5? new StringBuilder("x"): new StringBuffer("y");\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

并得到

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

由于传入的类实现了这两个接口,因此只需查看传入的和类型,您就可以\xe2\x80\x99t 找出 或 是否CharSequence是正确的合并类型。AppendableStringBuilderStringBuffer

\n\n

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

\n\n
class Example {\n    public static Comparable problematicMethod() {\n        return Math.random()>0.5? BigInteger.valueOf(123): Double.valueOf(1.23);\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

产生

\n\n
##original\nLocals: [], Stack: []\nLocals: [], Stack: [java.lang.CharSequence]\n##from ASM\nLocals: [], Stack: []\nLocals: [], Stack: [java.lang.AbstractStringBuilder]\n
Run Code Online (Sandbox Code Playgroud)\n\n

在这里,ASM\xe2\x80\x99s 结果是一个public类型,但这个公共基类没有\xe2\x80\x99t 实现所需的Comparable,所以这段代码实际上是损坏的。

\n\n
\n\n

对于所有使用 ASM\xe2\x80\x99sCOMPUTE_FRAMES选项的代码生成器来说,HotSpot\xe2\x80\x99s 验证器对接口类型有很大的容忍度,或者换句话说,它不\xe2\x80\x99s 是一个巨大的幸运。当两种类型中至少一种是接口时,xe2\x80\x99t 完全验证分配的正确性(这包括方法调用的接收者)。

\n\n

如果您想生成即使对于接口也能在验证器严格执行其工作的情况下幸存下来的代码,您不应该使用该选项并开始自己生成堆栈映射帧,而不是使用该选项并发出COMPUTE_FRAMES正确的visitFrame调用(或插入如果您\xe2\x80\x99 使用树 API,则为适当的节点)。

\n\n

人们似乎普遍担心这样做,但它\xe2\x80\x99 并没有那么复杂。如前所述,它基本上意味着说明您的代码生成器已经知道的内容。它\xe2\x80\x99s实际上不是试图找到一个通用类型,它\xe2\x80\x99s是关于指定你之后将使用什么,如果你的代码生成器是正确的,那么\xe2\x80\x99s已经它了,但是如果不是,ASM\xe2\x80\x99s 计算也可以\xe2\x80\x99 修复代码。

\n\n

继续看你的具体例子,在处理时,ThaiBuddhistDate你已经知道你正在分支合并点之后HijrahDate处理它们(我想),而 ASM 最终以实现特定的非类型结束,但如果该类型没有\xe2\ x80\x99t 存在,ASM 只是使用,因为它不\xe2\x80\x99t 考虑接口。如果 ASM 考虑接口,它必须在和 之间做出决定,两者都不比另一个更具体。它\xe2\x80\x99s根本无法用这种设计来解决。ChronoLocalDatepublicjava.lang.ObjectChronoLocalDateSerializable

\n\n

为了进一步说明, \xe2\x80\x9cmerge 传入类型 \xe2\x80\x9d 和 \xe2\x80\x9c 之间的结果有何不同\xe2\x80\x9d 将使用什么,请查看

\n\n
class Example {\n    public static void problematicMethod() {\n        if(Math.random()>0.5) {\n            java.awt.ScrollPane b = new java.awt.ScrollPane();\n        }\n        else {\n            javax.swing.JTabbedPane t = new javax.swing.JTabbedPane();\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n
class Example {\n    public static Appendable problematicMethod() {\n        return Math.random()>0.5? new StringBuilder("x"): new StringBuffer("y");\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

在这里,ASM 浪费了资源来找出深层类层次树中的公共基类,而只需声明 \xe2\x80\x9cdrop 变量\xe2\x80\x9d 就足够了\xe2\x80\xa6

\n