首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >与JSE JavaDoc相比,可以存在哪些类层次结构差异?

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

Stack Overflow用户
提问于 2018-03-12 00:34:51
回答 1查看 184关注 0票数 1

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

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

对象- Foo - Bar - FooBar

我可以在继承结构中的任何地方都有额外的类吗?

对象- Baz - Foo - Bar - FooBar

对象- Foo - Baz - Bar - FooBar

对象- Foo - Bar - Baz - FooBar

与接口类似:接口是否也可以从文档中未定义的其他接口继承,或者一个类是否可以有额外的独立接口或接口,这些接口或接口基于已定义的接口或甚至没有接口?

EN

回答 1

Stack Overflow用户

发布于 2018-03-14 01:14:52

您似乎正在使用COMPUTE_FRAMES选项,它将导致ASM库通过getCommonSuperClass合并通过可能的代码路径遇到的类型,类似于旧的验证器所做的事情,并且有点颠覆了堆栈映射表的概念。

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

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

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

代码语言:javascript
复制
class Example {
    public static CharSequence problematicMethod() {
        return Math.random()>0.5? new StringBuilder("x"): new StringBuffer("y");
    }
}

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

代码语言:javascript
复制
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);
}

这将打印出来

代码语言:javascript
复制
##original
Locals: [], Stack: []
Locals: [], Stack: [java.lang.CharSequence]
##from ASM
Locals: [], Stack: []
Locals: [], Stack: [java.lang.AbstractStringBuilder]

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

代码语言:javascript
复制
class Example {
    public static Appendable problematicMethod() {
        return Math.random()>0.5? new StringBuilder("x"): new StringBuffer("y");
    }
}

并获取

代码语言:javascript
复制
##original
Locals: [], Stack: []
Locals: [], Stack: [java.lang.Appendable]
##from ASM
Locals: [], Stack: []
Locals: [], Stack: [java.lang.AbstractStringBuilder]

由于传入的类实现了这两个接口,因此您无法通过查看传入的StringBuilderStringBuffer类型来确定是正确的合并类型是CharSequence还是Appendable

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

代码语言:javascript
复制
class Example {
    public static Comparable problematicMethod() {
        return Math.random()>0.5? BigInteger.valueOf(123): Double.valueOf(1.23);
    }
}

它会产生

代码语言:javascript
复制
##original
Locals: [], Stack: []
Locals: [], Stack: [java.lang.Comparable]
##from ASM
Locals: [], Stack: []
Locals: [], Stack: [java.lang.Number]

在这里,ASM的结果是一个public类型,但是这个公共基类没有实现所需的Comparable,所以这段代码实际上是错误的。

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

如果您希望生成的代码在严格执行验证器的工作(甚至对于接口)时仍然有效,那么您不应该使用该选项,而是自己开始生成堆栈映射框架,方法是不使用COMPUTE_FRAMES选项并发出正确的visitFrame调用(或者,如果您正在使用tree API,则插入适当的节点)。

这样做似乎有一种普遍的恐惧,但这并不复杂。如前所述,这基本上意味着你的代码生成器已经知道了什么。这实际上不是试图找到一个通用类型,而是指定你以后要使用什么,如果你的代码生成器是正确的,那么它已经是正确的了,但是如果不正确,ASM的计算也不能修复代码。

回到你的具体例子,在处理ThaiBuddhistDateHijrahDate时,你已经知道在分支合并点(我想)之后,你会把它们当作ChronoLocalDate来处理,而ASM最终会变成一个特定于实现的非public类型,但是如果这个类型不存在,ASM就会使用java.lang.Object,因为它不考虑接口。如果ASM考虑接口,它必须在ChronoLocalDateSerializable之间做出选择,两者都不是更具体。这种设计根本无法解决这个问题。

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

代码语言:javascript
复制
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();
        }
    }
}
代码语言:javascript
复制
##original
Locals: [], Stack: []
Locals: [], Stack: []
##from ASM
Locals: [], Stack: []
Locals: [java.awt.Container], Stack: []

在这里,ASM浪费了资源来找出深层类层次结构树中的公共基类,而只需声明“drop the variable”就足够了…

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/49222338

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档