Java 字节码 asm - 如何创建仅更改类名的类的克隆?

标签 java java-8 bytecode java-bytecode-asm bytecode-manipulation

Java asm - 如何创建仅更改类名的类的克隆?

我知道有一种使用 asm SimpleRemapper 修改类名的简单方法, 但我只想更改外部类名而不修改方法中使用的类名。 (请看下面的例子)

基本上如果我有一个目标类

public class Target {
  public Target clone(...) ...
  public int compare(another: Target) ...
}

我只是想创建一个看起来像这样的克隆:

public class ClonedTarget {
  public Target clone(...) ...
  public int compare(another: Target) ...
}

(请注意,clone 的返回类型和 compare 的 arg 类型没有改变。这是我的用例特意设置的)。

最佳答案

克隆一个类并更改名称,并且仅更改名称,即保留所有其他类引用原样,使用 ASM API 实际上非常容易。

ClassReader cr = new ClassReader(Target.class.getResourceAsStream("Target.class"));
ClassWriter cw = new ClassWriter(cr, 0);
cr.accept(new ClassVisitor(Opcodes.ASM5, cw) {
    @Override
    public void visit(int version, int access, String name,
                      String signature, String superName, String[] interfaces) {
        super.visit(version, access, "ClonedTarget", signature, superName, interfaces);
    }
}, 0);
byte[] code = cw.toByteArray();

链接 ClassReader 时用ClassWriter , ClassVisitor中间只需要覆盖它要更改的工件对应的那些方法。因此,要更改名称而不是其他,我们只需要覆盖 visit类声明的方法并将不同的名称传递给super方法。

通过将类读取器传递给类编写器的构造函数,我们甚至表示将只进行很少的更改,从而可以对转换过程进行后续优化,即常量池的大部分,以及方法的代码, 只会被复制到这里。


值得考虑其中的含义。在字节码级别,构造函数具有特殊名称 <init> ,所以它们一直是结果类的构造函数,不管它的名字是什么。调用父类(super class)构造函数的普通构造函数可能会继续在生成的类中工作。

ClonedTarget 上调用实例方法时对象,this引用的类型为 ClonedTarget .此基本属性不需要声明,因此在这方面没有需要调整的声明。

问题就出在这里。原始代码假定 this类型为 Target由于没有任何调整,复制的代码仍然错误地假定 this类型为 Target , 它可以以各种方式中断。

考虑:

public class Target {
  public Target clone() { return new Target(); }
  public int compare(Target t) { return 0;}
}

这看起来不受此问题的影响。生成的默认构造函数只是调用 super()并将继续工作。 compare方法有一个未使用的参数类型保留原样。和 clone()方法实例化 Target (不变)并返回它,匹配返回类型 Target (不变)。看起来不错。

但是这里看不到的是 clone方法重写方法 Object clone()继承自 java.lang.Object因此,将生成桥接方法。此桥接方法将具有声明 Object clone()并委托(delegate)给 Target clone()方法。问题是这个委托(delegate)是对 this 的调用调用目标的假定类型在调用指令中编码。这将导致 VerifierError .

一般来说,我们不能简单地区分哪些调用应用于this。并且在未更改的引用上,如参数或字段。它甚至不需要有一个明确的答案。考虑:

public void method(Target t, boolean b) {
    (b? this: t).otherMethod();
}

隐式假设 this类型为 Target , 它可以使用 this和一个 Target来自另一个来源的实例可互换。我们无法更改 this键入并保留参数类型而不重写代码。

其他问题与可见性有关。对于重命名的类, validator 将拒绝对 private 的未更改访问原始类(class)的成员。

除了失败 VerifyError ,有问题的代码可能会漏掉并在以后引起问题。考虑:

public class Target implements Cloneable {
    public Target duplicate() {
        try {
            return (Target)super.clone();
        } catch(CloneNotSupportedException ex) {
            throw new AssertionError();
        }
    }
}

自此 duplicate()不覆盖父类(super class)方法,不会有桥接方法和所有未更改的使用 Target从验证者的角度来看是正确的。

但是 clone() Object的方法|不返回 Target 的实例但是 this ’类,ClonedTarget在重命名的克隆中。所以这将失败并显示 ClassCastException , 仅在执行时。


这并不排除具有已知内容的类的工作用例。但一般来说,它非常脆弱。

关于Java 字节码 asm - 如何创建仅更改类名的类的克隆?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63319665/

相关文章:

java - 使用 Streams API 对集合中的 n 个随机不同元素执行操作

java - 如何在Java 8中传递多个参数和一个函数,然后执行带参数的函数?

java - 动态设置EditText的文本

java - 为什么我在执行 POST 请求时收到 405 Method Not Allowed

java - 无法打开文件 :E:\glassfish-5. 1.0\glassfish5\glassfish\domains\domain1/config/keystore.jks [ keystore 被篡改,或密码不正确]

java - ConcurrentHashMap中的forEach和forEachEntry有什么区别

python - 修改python字节码

java - 检查 Java 字节码是否包含调试符号

Java代理。做什么的?