Создание экземпляра объекта разделено на две инструкции. Это может затруднить работу с экземпляром объекта. На самом деле это была самая большая проблема во многих моих проектах. Простое выражение new Integer(0)
может быть скомпилировано в следующий код.
new java/lang/Integer ═╤═
dup ═╪═══╤═
iconst_0 │ │ ═╤═
invokespecial java/lang/Integer.<init>(I)V │ ═╧═══╧═
↓
Ссылка, созданная с помощью new
, не может использоваться, пока она не инициализирована с помощью invokespecial
. Следовательно, вы не можете ставить инструменты сразу после new
. В приведенном выше примере вы можете разместить приборы за invokespecial
. Однако вам нужно отличать guish от вызовов суперконструктора. Такой оператор, как super(0)
, может компилироваться в следующий код.
aload_0 ═╤═
iconst_0 │ ═╤═
invokespecial {super class}.<init>(I)V ═╧═══╧═
Эти два примера могут охватывать два наиболее распространенных случая, когда вызывается <init>
. Если вы ничего не сделаете со значением, созданным new Integer(0)
, компилятор также может решить пропустить инструкцию dup
. Я не уверен, действительно ли они это делают. В любом случае, первый пример wourld стал следующим кодом.
new java/lang/Integer ═╤═
iconst_0 │ ═╤═
invokespecial java/lang/Integer.<init>(I)V ═╧═══╧═
Это все еще можно обработать, вставив dup
за new
и invokestatic
за invokespecial
.
В любом случае, если вы хотите обрабатывать еще больше случаев exoti c, байт-код также может содержать такие инструкции, как dup_x1
или dup_x2
. GeneratorAdapter.box(Type)
фактически генерирует такой код. Некоторые «ленивые» манипуляторы байт-кода могут также вызывать инструкции new
, в которых экземпляр никогда не инициализируется до того, как он будет удален с помощью pop
. Как видите, работа с экземплярами объектов в байт-коде может занять много работы. Однако, в зависимости от вашей ситуации, вам может не потребоваться поддержка всех этих случаев. К сожалению, у меня нет опыта, чтобы оценить, какие дела вам нужно поддержать.
Вот пример, который не обрабатывает dup_x1
и подобные инструкции. Следовательно, он не будет работать с кодом, созданным GeneratorAdapter.box(Type)
. Кроме того, в моих тестах он работал неплохо. Для получения информации о стеке операндов используется AnalyzerAdapter
.
public final class InstrumentationMethodVisitor extends AnalyzerAdapter {
public InstrumentationMethodVisitor(
String owner, int access, String name, String descriptor,
MethodVisitor methodVisitor) {
super(ASM8, owner, access, name, descriptor, methodVisitor);
}
@Override
public void visitTypeInsn(int opcode, String type) {
super.visitTypeInsn(opcode, type);
if (opcode == NEW && stack != null) {
mv.visitInsn(DUP);
}
}
@Override
public void visitInsn(int opcode) {
if (opcode == POP && stack != null && stack.get(stack.size() - 1) instanceof Label) {
mv.visitInsn(POP);
}
super.visitInsn(opcode);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
if (opcode == INVOKESPECIAL && name.equals("<init>")) {
boolean hasRelatedNew = stack != null && stack.get(stack.size() - getAmountOfArgumentSlots(descriptor) - 1) instanceof Label;
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
if (hasRelatedNew) {
mv.visitMethodInsn(
INVOKESTATIC,
"org/myekstazi/agent/PurityRecorder",
"object_new",
"(Ljava/lang/Object;)V",
false);
}
}
else {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
}
private static int getAmountOfArgumentSlots(String descriptor) {
int slots = 0;
for (Type type : Type.getArgumentTypes(descriptor)) {
slots += type.getSize();
}
return slots;
}
}