编程语言
首页 > 编程语言> > 与JSE JavaDoc相比,可以存在哪些类层次结构差异?

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

作者:互联网

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

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

对象-Foo-酒吧-FooBar

到处都可以在继承结构中添加其他类吗?

对象-巴兹-富-酒吧-富巴尔

物件-Foo-巴兹-酒吧-FooBar

物件-Foo-酒吧-Baz-FooBar

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

解决方法:

似乎您正在使用COMPUTE_FRAMES选项,这将导致ASM库通过getCommonSuperClass合并可能的代码路径中遇到的类型,这与旧的验证程序的操作类似,并且在某种程度上使堆栈映射表的概念发生了变化.

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

正确的类型是随后将需要的类型,当然,它也应与通过所有可能的代码路径提供的类型兼容,验证者将/应检查该类型.如果您的代码生成器以生成有效代码的方式进行设计,则指定后续所需的类型应足以创建有效的堆栈映射表条目,但是传递给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<num; ix++) {
        Object o = array[ix];
        if(o==Opcodes.UNINITIALIZED_THIS) sb.append("this <uninit>");
        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中的StringBuilder和StringBuffer并发现它们都实现了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]

由于传入的类都实现了这两个接口,因此仅通过查看传入的StringBuilder和StringBuffer类型就无法确定CharSequence或Appendable是正确的合并类型.

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

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的结果是一个公共类型,但是此通用基类未实现所需的Comparable,因此该代码实际上被破坏了.

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

如果您想生成在验证程序中仍然严格执行其任务的代码,即使对于接口而言,您也不应使用该选项,而无需使用COMPUTE_FRAMES选项并发出正确的visitFrame调用(或插入适当的代码)来开始生成堆栈映射框架节点(如果您使用的是树API).

似乎普遍担心这样做,但这并不复杂.如前所述,它基本上意味着说明您的代码生成器已经知道的内容.实际上,这并不是要查找常见的类型,而是要指定以后要使用的类型,如果代码生成器是正确的,那么就已经确定了,但是,否则,ASM的计算也无法修复该代码.

待在您的特定示例中,在处理ThaiBuddhistDate和HijrahDate时,您已经知道在分支合并点之后(我想是)将它们作为ChronoLocalDate处理,而ASM最终以实现特定的非公共类型结束,但是如果该类型不存在,ASM仅使用java.lang.Object,因为它不考虑接口.如果ASM考虑了接口,则必须在ChronoLocalDate和Serializable之间进行选择,两者都不比另一个更具体.这种设计根本无法解决.

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

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浪费了资源,无法在一个深层的类层次结构树中查找公共基类,而仅声明“删除变量”就足够了……

标签:java,jvm,specifications,hierarchy
来源: https://codeday.me/bug/20191013/1910244.html