tangguo

Java ASM GeneratorAdapter变量命名

java

我正在生成一个简单的类,并且无法注入适当的变量名。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 <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)它的方法。

除了变量名之外,发出的字节码没有其他问题。当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()方法未向映射添加任何内容。

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

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


阅读 282

收藏
2020-11-30

共1个答案

小编典典

由于GeneratorAdapterextendsLocalVariablesSorter的目的是适应所有访问者调用,因此与引入的专用方法不同,所有访问者API的方法都将得到适应GeneratorAdapter。这种设计允许将新代码插入到现有方法中,在旧方法中,旧代码通过访问者API报告。

因此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)

2020-11-30