Java 字节码 asm - 如何创建仅更改 class 名称的 class 的克隆?
Java bytecode asm - How can I create a clone of a class with only the class name changed?
Java asm - 如何创建仅更改 class 名称的 class 的克隆?
我知道有一种使用 asm SimpleRemapper、 修改 class 名称的简单方法,但我只想更改外部 class 名称而不修改class 方法中使用的名称。 (请看下面的例子)
基本上如果我有一个目标class
public class Target {
public Target clone(...) ...
public int compare(another: Target) ...
}
我只想创建一个看起来像这样的克隆:
public class ClonedTarget {
public Target clone(...) ...
public int compare(another: Target) ...
}
(请注意,clone
的 return 类型和 compare
的 arg 类型没有改变。这是我的用例特意设置的)。
克隆 class 并仅更改名称,即保留所有其他 class 引用 as-is,实际上使用 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
只需要覆盖对应于它要更改的工件的那些方法。因此,要更改名称而不是其他,我们只需要覆盖 class 声明的 visit
方法并将不同的名称传递给 super
方法。
通过将 class reader 传递给 class 编写器的构造函数,我们甚至表示将只进行很少的更改,从而可以对转换过程进行后续优化,即大多数常量池,以及方法的代码,将被复制到这里。
值得考虑其中的含义。在字节码级别,构造函数具有特殊名称 <init>
,因此它们在结果 class 中一直是构造函数,而不管其名称如何。调用 superclass 构造函数的普通构造函数可能会继续在结果 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
方法有一个未使用的参数类型 as-is。 clone()
方法实例化 Target
(不变)和 returns 它,匹配 return 类型 Target
(不变)。看起来不错。
但是这里看不到的是,clone
方法覆盖了继承自java.lang.Object
的方法Object clone()
,因此会生成桥接方法。此桥接方法将具有声明 Object clone()
并仅委托给 Target clone()
方法。问题在于此委托是对 this
的调用,并且调用目标的假定类型在调用指令中进行了编码。这将导致 VerifierError
.
通常,我们不能简单地区分哪些调用应用于 this
哪些调用未更改,如参数或字段。它甚至不需要有一个明确的答案。考虑:
public void method(Target t, boolean b) {
(b? this: t).otherMethod();
}
隐式假设 this
具有类型 Target
,它可以互换地使用 this
和来自另一个来源的 Target
实例。我们不能改变this
类型而保留参数类型而不重写代码。
其他与可见性相关的问题。对于重命名的 class,验证者将拒绝对原始 class.
的 private
成员的未更改访问
除了失败 VerifyError
之外,有问题的代码可能会漏掉并在以后引起问题。考虑:
public class Target implements Cloneable {
public Target duplicate() {
try {
return (Target)super.clone();
} catch(CloneNotSupportedException ex) {
throw new AssertionError();
}
}
}
由于此 duplicate()
不会覆盖 superclass 方法,因此不会有桥接方法,并且 Target
的所有未更改使用从验证者的角度来看都是正确的。
但是 Object
的 clone()
方法不是 return Target
的实例,而是 this
' class, ClonedTarget
在重命名的克隆中。所以这将失败并显示 ClassCastException
,仅当被执行时。
这并不排除具有已知内容的 class 的工作用例。但总的来说,它很脆弱。
Java asm - 如何创建仅更改 class 名称的 class 的克隆?
我知道有一种使用 asm SimpleRemapper、 修改 class 名称的简单方法,但我只想更改外部 class 名称而不修改class 方法中使用的名称。 (请看下面的例子)
基本上如果我有一个目标class
public class Target {
public Target clone(...) ...
public int compare(another: Target) ...
}
我只想创建一个看起来像这样的克隆:
public class ClonedTarget {
public Target clone(...) ...
public int compare(another: Target) ...
}
(请注意,clone
的 return 类型和 compare
的 arg 类型没有改变。这是我的用例特意设置的)。
克隆 class 并仅更改名称,即保留所有其他 class 引用 as-is,实际上使用 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
只需要覆盖对应于它要更改的工件的那些方法。因此,要更改名称而不是其他,我们只需要覆盖 class 声明的 visit
方法并将不同的名称传递给 super
方法。
通过将 class reader 传递给 class 编写器的构造函数,我们甚至表示将只进行很少的更改,从而可以对转换过程进行后续优化,即大多数常量池,以及方法的代码,将被复制到这里。
值得考虑其中的含义。在字节码级别,构造函数具有特殊名称 <init>
,因此它们在结果 class 中一直是构造函数,而不管其名称如何。调用 superclass 构造函数的普通构造函数可能会继续在结果 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
方法有一个未使用的参数类型 as-is。 clone()
方法实例化 Target
(不变)和 returns 它,匹配 return 类型 Target
(不变)。看起来不错。
但是这里看不到的是,clone
方法覆盖了继承自java.lang.Object
的方法Object clone()
,因此会生成桥接方法。此桥接方法将具有声明 Object clone()
并仅委托给 Target clone()
方法。问题在于此委托是对 this
的调用,并且调用目标的假定类型在调用指令中进行了编码。这将导致 VerifierError
.
通常,我们不能简单地区分哪些调用应用于 this
哪些调用未更改,如参数或字段。它甚至不需要有一个明确的答案。考虑:
public void method(Target t, boolean b) {
(b? this: t).otherMethod();
}
隐式假设 this
具有类型 Target
,它可以互换地使用 this
和来自另一个来源的 Target
实例。我们不能改变this
类型而保留参数类型而不重写代码。
其他与可见性相关的问题。对于重命名的 class,验证者将拒绝对原始 class.
的private
成员的未更改访问
除了失败 VerifyError
之外,有问题的代码可能会漏掉并在以后引起问题。考虑:
public class Target implements Cloneable {
public Target duplicate() {
try {
return (Target)super.clone();
} catch(CloneNotSupportedException ex) {
throw new AssertionError();
}
}
}
由于此 duplicate()
不会覆盖 superclass 方法,因此不会有桥接方法,并且 Target
的所有未更改使用从验证者的角度来看都是正确的。
但是 Object
的 clone()
方法不是 return Target
的实例,而是 this
' class, ClonedTarget
在重命名的克隆中。所以这将失败并显示 ClassCastException
,仅当被执行时。
这并不排除具有已知内容的 class 的工作用例。但总的来说,它很脆弱。