Java ASM GeneratorAdapter变量命名

我正在生成一个简单的类,无法注入适当的变量名称。 ASM版本是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 ()"); 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); } } 

我正在使用GeneratorAdapter来简化代码生成。 由于GeneratorAdapterinheritance自LocalVariablesSorter ,我假设允许使用newLocal(Type)方法。

除了变量的名称之外,发出的字节码没有任何问题。 visitLocalVariable()方法时,不是为变量赋值,而是在字节码中创建一个新名称。

发射字节码:

 // class version 52.0 (52) // access flags 0x1 public class com/test/Sub { // access flags 0x1 public ()V ALOAD 0 INVOKESPECIAL java/lang/Object. ()V LDC 42.42 DSTORE 1 L0 RETURN L1 LOCALVARIABLE x D L0 L1 3 MAXSTACK = 2 MAXLOCALS = 5 } 

我在visitLocalVariable()使用newLocal()调用提供的相同变量索引。 但是在字节码中,映射索引是3而不是1 。 如果变量具有“较短”类型(如int那么索引将为2并且仍然不应该为1

根据我的观察,这是因为以下原因。 LocalVariablesSorter维护从旧变量索引到新变量索引的映射。 它还会覆盖方法visitLocalVariable并且在向访问者链委托调用之前,它会根据映射计算newIndexnewIndex通过另一个私有方法remap() 。 此方法检查给定变量的映射是否已存在,如果不存在,则创建新映射。 我认为问题在于newLocal()方法不会向映射添加任何内容。

另外我可以从ASM源看到, GeneratorAdapter visitVarInsn()委托visitVarInsn()调用链而不是调用LocalVariablesSorter的实现。 因为它在LocalVariablesSorter实现中,所以为变量索引调用remap()方法并更新映射。

因此,我的问题是如何使用GeneratorAdapter以便在发出的字节码中正确命名变量,或者如何将GeneratorAdapterLocalVariablesSorter组合在一起,以便它们一起正常工作?

由于GeneratorAdapter扩展了LocalVariablesSorter ,其目的是调整所有访问者调用,所以作为访问者API的一部分的所有方法都得到了调整,这与GeneratorAdapter引入的专用方法不同。 此设计允许将新代码插入到现有方法中,其中旧代码通过访问者API进行报告。

因此,必须在目标MethodVisitor上调用visitLocalVariable方法(它是访问者API的一部分),绕过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 ()"); 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这样的任何便利包装GeneratorAdapter 。 它并不复杂,虽然它需要更多的知识,但是,在处理Java字节码和类文件时,开发人员应该知道它…

 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); String name = "com.test.Sub"; String superClName = "java/lang/Object", ctorName = "", 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)