Клонирането на клас и промяната на името и само на името, т.е. оставянето на всяка друга препратка към клас, всъщност е много лесно с 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>
, така че те продължават да бъдат конструктори в резултантния клас, независимо от името му. Тривиалните конструктори, извикващи конструктор на суперклас, могат да продължат да работят в получения клас.
Когато се извикват методи на екземпляр на 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()
и просто ще делегира на метода Target clone()
. Проблемът е, че това делегиране е извикване на this
и предполагаемият тип на целта за извикване е кодиран в инструкцията за извикване. Това ще доведе до VerifierError
.
Като цяло не можем просто да различим кои извиквания се прилагат към this
и кои към непроменена препратка, като параметър или поле. Дори не е необходимо да има категоричен отговор. Обмисли:
public void method(Target t, boolean b) {
(b? this: t).otherMethod();
}
Имплицитно приемайки, че this
има тип Target
, той може да използва this
и Target
екземпляр от друг източник взаимозаменяемо. Не можем да променим типа this
и да запазим типа на параметъра, без да пренапишем кода.
Други проблеми възникват с видимостта. За преименувания клас верификаторът ще отхвърли непроменен достъп до private
члена на оригиналния клас.
Освен неуспех с VerifyError
, проблемният код може да се промъкне и да причини проблеми по-късно. Обмисли:
public class Target implements Cloneable {
public Target duplicate() {
try {
return (Target)super.clone();
} catch(CloneNotSupportedException ex) {
throw new AssertionError();
}
}
}
Тъй като този duplicate()
не отменя метод на суперклас, няма да има мостов метод и всички непроменени употреби на Target
са правилни от гледна точка на проверяващия.
Но методът clone()
на Object
не връща екземпляр на Target
, а на класа this
’, ClonedTarget
в преименувания клонинг. Така че това ще се провали с ClassCastException
, само когато се изпълнява.
Това не изключва работни случаи на употреба за клас с известно съдържание. Но като цяло е много крехко.
person
Holger
schedule
10.08.2020