Клонирование класса и изменение имени и только имени, т. е. оставить все остальные ссылки на классы как есть, на самом деле очень просто с 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