Здесь есть две проблемы.
Во-первых, кажется, что когда вызывается Instrumentation.retransformClasses
, ошибки с преобразованным кодом, такие как VerifyError
, не сообщаются JVM, а вместо этогопросто перейдите к старому коду.
Я не вижу здесь способа изменить поведение JVM.Стоит создать дополнительную тестовую среду, в которой вы используете другой метод для активации кода, такой как преобразование во время загрузки или просто статическое преобразование скомпилированных классов, и пытаетесь загрузить их.Это может быть в дополнение к производственному коду, который использует тот же код преобразования с retransformClasses
, когда эти тесты не показывают ошибок.
Кстати, когда вы реализуете ClassFileTransformer
, вы должны передать *Массив 1011 *, полученный в качестве параметра для метода transform
для конструктора ClassReader(byte[])
вместо использования конструктора ClassReader(String)
.
Во-вторых, расположение кода последнего сообщенного номера строки такжецель филиала.Помните, что разрывы строк не генерируют код, поэтому конец цикла совпадает с началом оператора return
.
ASM сообщит о связанных артефактах в следующем порядке:
visitLabel
с экземпляром Label
, связанным с местоположением кода visitLineNumber
с новым номером строки и Label
из предыдущего шага visitFrame
чтобы сообщить фрейм карты стека, связанный с этим местоположением кода (поскольку он является целью ветвления)
Вы вставляете новую инструкцию при вызове visitLineNumber
, в результате чего цель ветвления становится до этой новой инструкции, поскольку вы делегировали visitLabel
перед ней.Но вызов visitFrame
делегируется после , когда новые инструкции были вставлены, следовательно, больше не связаны с целью перехода.Это приводит к VerifyError
, поскольку для каждой цели ветвления обязательно иметь кадр стековой карты.
Простое, но дорогостоящее решение состоит в том, чтобы не использовать кадры стековой карты исходного класса, но позволить ASM пересчитать зановоих.Т.е.
public static byte[] getTransformed(byte[] originalCode) {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
ClassReader cr = new ClassReader(originalCode);
ClassVisitor tcv = new TransformClassVisitor(cw);
cr.accept(tcv, ClassReader.SKIP_FRAMES);
return cw.toByteArray();
}
Кстати, когда вы сохраняете большую часть исходного кода, но вставляете только некоторые новые операторы, полезно оптимизировать процесс путем передачи ClassReader
в ClassWriter
's constructor :
public static byte[] getTransformed(byte[] originalCode) {
ClassReader cr = new ClassReader(originalCode);
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
ClassVisitor tcv = new TransformClassVisitor(cw);
cr.accept(tcv, ClassReader.SKIP_FRAMES);
return cw.toByteArray();
}
Более эффективное решение, которое не пересчитывает кадры стековой карты (поскольку исходные кадры все еще подходят для такого простого преобразования), не так просто с API ASM.Единственная идея, которую я имею до сих пор, это отложить вставку новой инструкции до тех пор, пока фрейм не будет посещен, если он есть.К сожалению, это подразумевает переопределение всех visit
методов для инструкций:
Оставайтесь с
public static byte[] getTransformed(byte[] originalCode) {
ClassReader cr = new ClassReader(originalCode);
ClassWriter cw = new ClassWriter(cr, 0);
ClassVisitor tcv = new TransformClassVisitor(cw);
cr.accept(tcv, 0);
return cw.toByteArray();
}
и используйте
static class Transformator extends MethodVisitor {
int lastLineNumber;
public Transformator(MethodVisitor mv) {
super(Opcodes.ASM5, mv);
}
public void visitLineNumber(int line, Label start) {
lastLineNumber = line;
super.visitLineNumber(line, start);
}
private void checkLineNumber() {
if(lastLineNumber > 0) {
mv.visitMethodInsn(Opcodes.INVOKESTATIC, classpath,"visitLine","()V", false);
lastLineNumber = 0;
}
}
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
checkLineNumber();
super.visitTryCatchBlock(start, end, handler, type);
}
public void visitMultiANewArrayInsn(String descriptor, int numDimensions) {
checkLineNumber();
super.visitMultiANewArrayInsn(descriptor, numDimensions);
}
public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
checkLineNumber();
super.visitLookupSwitchInsn(dflt, keys, labels);
}
public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
checkLineNumber();
super.visitTableSwitchInsn(min, max, dflt, labels);
}
public void visitIincInsn(int var, int increment) {
checkLineNumber();
super.visitIincInsn(var, increment);
}
public void visitLdcInsn(Object value) {
checkLineNumber();
super.visitLdcInsn(value);
}
public void visitJumpInsn(int opcode, Label label) {
checkLineNumber();
super.visitJumpInsn(opcode, label);
}
public void visitInvokeDynamicInsn(
String name, String desc, Handle bsmHandle, Object... bsmArg) {
checkLineNumber();
super.visitInvokeDynamicInsn(name, desc, bsmHandle, bsmArg);
}
public void visitMethodInsn(
int opcode, String owner, String name, String desc, boolean iface) {
checkLineNumber();
super.visitMethodInsn(opcode, owner, name, desc, iface);
}
public void visitFieldInsn(int opcode, String owner, String name,String descriptor) {
checkLineNumber();
super.visitFieldInsn(opcode, owner, name, descriptor);
}
public void visitTypeInsn(int opcode, String type) {
checkLineNumber();
super.visitTypeInsn(opcode, type);
}
public void visitVarInsn(int opcode, int var) {
checkLineNumber();
super.visitVarInsn(opcode, var);
}
public void visitIntInsn(int opcode, int operand) {
checkLineNumber();
super.visitIntInsn(opcode, operand);
}
public void visitInsn(int opcode) {
checkLineNumber();
super.visitInsn(opcode);
}
}
К сожалению, модель посетителя ASM не имеет preVisitInstr()
или что-то подобное.
Обратите внимание, что при таком дизайне также невозможно ошибочно вводить инструкции после последней инструкции метода, поскольку введенные инструкции всегда помещаются перед другой инструкцией.