Java ASM getMergedType和getCommonSuperClass出现问题

Java ASM getMergedType和getCommonSuperClass出现问题,java,spring-boot,java-bytecode-asm,Java,Spring Boot,Java Bytecode Asm,我使用ASM更新类堆栈映射,但当ASMgetMergedType时,会发生以下异常: java.lang.RuntimeException: java.io.IOException:找不到IntefaceImplA的资源 如果不使用asm修改类方法,它确实可以正常工作。 我定义了两个接口A和B:IntefaceImplA和 IntefaceImplB 我的环境源代码: IntefaceA.java public interface IntefaceA { void inteface();

我使用ASM更新类堆栈映射,但当ASM
getMergedType
时,会发生以下异常:

java.lang.RuntimeException:
java.io.IOException:找不到IntefaceImplA的资源

如果不使用asm修改类方法,它确实可以正常工作。
我定义了两个接口A和B:
IntefaceImplA
IntefaceImplB

我的环境源代码:

IntefaceA.java

public interface IntefaceA {
    void inteface();
}
public class IntefaceImplA implements IntefaceA {
@Override
public void inteface() {

}
}
public class IntefaceImplB implements IntefaceA {
@Override
public void inteface() {

}
}
public class Test {
public IntefaceA getImpl(boolean b) {
    IntefaceA a = b ? new IntefaceImplA() : new IntefaceImplB();
    return a;
}
public class Main {
public static void main(String args[]) {
....
if (a instance of Test) {
..
...
}
}
}
IntefaceImplA.java

public interface IntefaceA {
    void inteface();
}
public class IntefaceImplA implements IntefaceA {
@Override
public void inteface() {

}
}
public class IntefaceImplB implements IntefaceA {
@Override
public void inteface() {

}
}
public class Test {
public IntefaceA getImpl(boolean b) {
    IntefaceA a = b ? new IntefaceImplA() : new IntefaceImplB();
    return a;
}
public class Main {
public static void main(String args[]) {
....
if (a instance of Test) {
..
...
}
}
}
IntefaceImplB.java

public interface IntefaceA {
    void inteface();
}
public class IntefaceImplA implements IntefaceA {
@Override
public void inteface() {

}
}
public class IntefaceImplB implements IntefaceA {
@Override
public void inteface() {

}
}
public class Test {
public IntefaceA getImpl(boolean b) {
    IntefaceA a = b ? new IntefaceImplA() : new IntefaceImplB();
    return a;
}
public class Main {
public static void main(String args[]) {
....
if (a instance of Test) {
..
...
}
}
}
Test.java

public interface IntefaceA {
    void inteface();
}
public class IntefaceImplA implements IntefaceA {
@Override
public void inteface() {

}
}
public class IntefaceImplB implements IntefaceA {
@Override
public void inteface() {

}
}
public class Test {
public IntefaceA getImpl(boolean b) {
    IntefaceA a = b ? new IntefaceImplA() : new IntefaceImplB();
    return a;
}
public class Main {
public static void main(String args[]) {
....
if (a instance of Test) {
..
...
}
}
}
}

Main.java

public interface IntefaceA {
    void inteface();
}
public class IntefaceImplA implements IntefaceA {
@Override
public void inteface() {

}
}
public class IntefaceImplB implements IntefaceA {
@Override
public void inteface() {

}
}
public class Test {
public IntefaceA getImpl(boolean b) {
    IntefaceA a = b ? new IntefaceImplA() : new IntefaceImplB();
    return a;
}
public class Main {
public static void main(String args[]) {
....
if (a instance of Test) {
..
...
}
}
}
编译运行程序jar后,从jar中手动删除IntefaceImplA.class和IntefaceA.class。为什么我要删除这些类文件,因为spring总是喜欢这样做

runner jar可以在没有ASM的情况下正常运行,但使用ASM会出现异常。因为asm想要为IntefaceImplA和IntefaceImplB获取合并类型,但是IntefaceImplA被我删除了。 在调查ASM ClassWriter源代码后,我发现了以下代码:

protected String getCommonSuperClass(String type1, String type2) 
{
    ClassLoader classLoader = this.getClass().getClassLoader();
    Class c;
    Class d;
    try {
        c = Class.forName(type1.replace('/', '.'), false, classLoader);
        d = Class.forName(type2.replace('/', '.'), false, classLoader);
    } catch (Exception var7) {
        throw new RuntimeException(var7.toString());
    }

    if(c.isAssignableFrom(d)) {
        return type1;
    } else if(d.isAssignableFrom(c)) {
        return type2;
    } else if(!c.isInterface() && !d.isInterface()) {
        do {
            c = c.getSuperclass();
        } while(!c.isAssignableFrom(d));

        return c.getName().replace('.', '/');
    } else {
        return "java/lang/Object";
    }
}
实际上,我删除了相关的类文件,类加载器找不到该类。但如果没有asm,程序将正常工作。

我是否应该增强对getCommonSuperClass方法的覆盖,如果发生异常,则为其返回java/lang/Object?这很有趣

通常,覆盖
getCommonSuperClass
以使用不同的策略(例如不加载类)是一个有效的用例。如各国所述:

此方法的默认实现加载两个给定类,并使用java.lang.Class方法查找公共超类。可以重写它以其他方式计算这个公共超类型,特别是在不实际加载任何类的情况下,或者考虑这个类编写器当前生成的类,当然,由于它正在构造中,所以不能加载这个类

除了其中一个或两个参数可能是您当前正在构造(或实质性更改)的类之外,代码转换工具的上下文可能不是类最终运行的上下文,因此它们不必在该上下文中通过
Class.forName
访问。由于
Class.forName
使用调用方的上下文,即ASM的
ClassWriter
,因此即使在使用ASM的代码上下文中可以访问该类,ASM也可能无法访问该类(如果涉及不同的类装入器)

另一个有效的方案是,通过使用已经可用的元信息,而不实际加载类,来更有效地解决请求

但是,当然,仅仅返回
“java/lang/Object”
并不是一个有效的解决方案。虽然这确实是每个参数的常见超级类型,但它不一定是代码的正确类型。以你为例

public IntefaceA getImpl(boolean b) {
    IntefaceA a = b ? new IntefaceImplA() : new IntefaceImplB();
    return a;
}
IntefaceImplA
IntefaceImplB
的通用超类型不仅需要验证将任一类型分配给它的有效性,它还是条件表达式的结果类型,必须可分配给方法的返回类型。如果将
java/lang/Object
用作公共超类型,验证器将拒绝该代码,因为它不能分配给
IntefaceA

最初的stackmap(很可能报告
IntefaceA
为common super)将被验证者接受,因为该类型与方法的返回类型相同,因此即使不加载该类型,也可以认为它是可分配的。测试,无论是
IntefaceImplA
还是
IntefaceImplB
,是否可分配给指定的公共类型,可能会推迟到实际加载这些类型的点,并且由于您说过,您删除了
IntefaceA
,这永远不会发生

声明的返回类型不存在的方法根本无法工作。对于“没有asm,程序正常工作”这一观察结果的唯一解释是,在测试期间从未调用过此方法。您很可能通过删除正在使用的类在软件中创建了一个定时炸弹

不清楚你为什么这么做。你的解释“因为春天总是喜欢做这件事”是不可理解的


但是您可以使用覆盖方法获得与未修改代码相同的行为。返回
java/lang/Object
,它就是不起作用。你可以用

@Override
protected String getCommonSuperClass(String type1, String type2) {
    if(type1.matches("IntefaceImpl[AB]") && type2.matches("IntefaceImpl[AB]"))
        return "IntefaceA";
    return super.getCommonSuperClass(type1, type2);
}
当然,如果删除了更多的类文件,则必须添加更多的特殊情况

完全不同的方法是不使用该选项。此选项意味着ASM将从头开始重新计算所有堆栈映射帧,这对懒惰的程序员来说是非常好的,但如果您只是在现有类上进行少量代码转换,则意味着大量不必要的工作,当然,这会要求有一个工作的
getCommonSuperClass
方法

如果没有该选项,
ClassWriter
将只复制
ClassReader
报告的帧,因此所有未更改的方法也将具有未更改的堆栈映射。您必须关心更改其代码的方法,但是对于许多典型的代码转换任务,您仍然可以保留原始帧。例如,如果您只是将方法调用重定向到与签名兼容的目标,或注入使堆栈保持与之前状态相同的日志记录语句,则仍然可以保留原始帧,这是自动发生的。请注意的存在,它允许更有效地传输您不更改的方法

只有在更改分支结构或插入带有分支的代码时,才需要注意框架。但即便如此,学习如何做到这一点通常也是值得的,因为自动计算是安静的