我正在生成一个简单的类,并且无法注入适当的变量名。ASM版本是5.2。
5.2
这是代码:
package com.test; import org.objectweb.asm.*; import org.objectweb.asm.commons.GeneratorAdapter; import org.objectweb.asm.commons.Method; import java.nio.file.Files; import java.nio.file.Paths; public class Main { public static void main(String[] args) throws Exception { ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); String name = "com.test.Sub"; cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, name.replace('.', '/'), null, "java/lang/Object", null); Method ctor = Method.getMethod("void <init>()"); GeneratorAdapter mg = new GeneratorAdapter(Opcodes.ACC_PUBLIC, ctor, null, null, cw); mg.visitCode(); mg.loadThis(); mg.invokeConstructor(Type.getType(Object.class), ctor); int var = mg.newLocal(Type.INT_TYPE); mg.push(42.42); mg.storeLocal(var); Label varLabel = mg.mark(); mg.returnValue(); Label endLabel = mg.mark(); mg.visitLocalVariable("x", "D", null, varLabel, endLabel, var); mg.endMethod(); cw.visitEnd(); byte[] bytes = cw.toByteArray(); Files.write(Paths.get(name + ".class"), bytes); } }
我正在使用AGeneratorAdapter来简化代码生成。由于GeneratorAdapter继承自LocalVariablesSorter,因此我假定它被允许使用newLocal(Type)它的方法。
AGeneratorAdapter
GeneratorAdapter
LocalVariablesSorter
newLocal(Type)
除了变量名之外,发出的字节码没有其他问题。当visitLocalVariable()方法被调用,而不是给变量指定名称的它会在字节码一个新的。
visitLocalVariable()
发出的字节码:
// class version 52.0 (52) // access flags 0x1 public class com/test/Sub { // access flags 0x1 public <init>()V ALOAD 0 INVOKESPECIAL java/lang/Object.<init> ()V LDC 42.42 DSTORE 1 L0 RETURN L1 LOCALVARIABLE x D L0 L1 3 MAXSTACK = 2 MAXLOCALS = 5 }
我正在使用newLocal()调用中提供的变量索引visitLocalVariable()。但是在字节码中映射的索引3不是1。如果变量具有“更短”的类型,例如int索引,则索引应该是2,但仍然不是1。
根据我的观察,发生这种情况的原因如下。LocalVariablesSorter维护从旧变量索引到新变量索引的映射。它还会覆盖方法,visitLocalVariable并在将访问链下放一个呼叫之前,它会根据newIndex映射计算a 。将newIndex经由另一私人方法计算remap()。此方法检查给定变量的映射是否已经存在,如果不存在,则创建一个新的映射。我看到的问题是该newLocal()方法未向映射添加任何内容。
visitLocalVariable
remap()
newLocal()
从ASM的源代码中我还可以看到,storeInsn()在GeneratorAdapter委托中visitVarInsn()调用了链而不是调用的实现LocalVariablesSorter。因为它是在LocalVariablesSorter实现中,所以remap()`将为变量索引调用该方法,并更新映射。
storeInsn()
visitVarInsn()
。因为它是在
实现中,所以
因此,我的问题是如何使用GeneratorAdapter这样的变量,以便在发出的字节码中正确命名变量,或者如何在链中进行组合GeneratorAdapter以LocalVariablesSorter使它们正常工作?
由于GeneratorAdapterextendsLocalVariablesSorter的目的是适应所有访问者调用,因此与引入的专用方法不同,所有访问者API的方法都将得到适应GeneratorAdapter。这种设计允许将新代码插入到现有方法中,在旧方法中,旧代码通过访问者API报告。
GeneratorAdapterextendsLocalVariablesSorter
因此visitLocalVariable,作为访问者API一部分的方法必须MethodVisitor绕过目标在目标上调用LocalVariablesSorter:
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); String name = "com.test.Sub"; cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, name.replace('.', '/'), null, "java/lang/Object", null); Method ctor = Method.getMethod("void <init>()"); MethodVisitor direct = cw.visitMethod( Opcodes.ACC_PUBLIC, ctor.getName(), ctor.getDescriptor(), null, null); GeneratorAdapter mg = new GeneratorAdapter(Opcodes.ACC_PUBLIC, ctor, direct); mg.visitCode(); mg.loadThis(); mg.invokeConstructor(Type.getType(Object.class), ctor); int var = mg.newLocal(Type.DOUBLE_TYPE); mg.push(42.42); mg.storeLocal(var); Label varLabel = mg.mark(); mg.returnValue(); Label endLabel = mg.mark(); direct.visitLocalVariable("x", "D", null, varLabel, endLabel, var); mg.endMethod(); cw.visitEnd(); byte[] bytes = cw.toByteArray(); Files.write(Paths.get(name + ".class"), bytes);
由于这可能会造成混淆,因此,这里的替代方法MethodVisitor完全可以直接在目标上工作,而无需使用任何便利包装GeneratorAdapter。它并不复杂,尽管需要更多的知识,但是,它是开发人员在处理Java字节码和类文件时应该拥有的知识……
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); String name = "com.test.Sub"; String superClName = "java/lang/Object", ctorName = "<init>", ctorDesc = "()V"; cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, name.replace('.','/'), null, superClName, null); MethodVisitor direct = cw.visitMethod(Opcodes.ACC_PUBLIC, ctorName, ctorDesc, null, null); direct.visitCode(); // "this" is alway 0 (zero) and for parameterless methods the next var location is 1 (one) int thisVar = 0, var = 1; direct.visitVarInsn(Opcodes.ALOAD, thisVar); direct.visitMethodInsn(Opcodes.INVOKESPECIAL, superClName, ctorName, ctorDesc, false); direct.visitLdcInsn(42.42); Label varLabel = new Label(), endLabel = new Label(); direct.visitVarInsn(Opcodes.DSTORE, var); direct.visitLabel(varLabel); direct.visitInsn(Opcodes.RETURN); direct.visitLabel(endLabel); direct.visitLocalVariable("x", "D", null, varLabel, endLabel, var); direct.visitMaxs(-1, -1);// no actual values, using COMPUTE_FRAMES direct.visitEnd(); cw.visitEnd(); byte[] bytes = cw.toByteArray(); Files.write(Paths.get(name + ".class"), bytes);
如果您不满意直接使用()V无参数void方法,则仍可以Method像之前或之前那样使用对象Type.getMethodDescriptor(Type.VOID_TYPE)