【问题标题】:Java ASM GeneratorAdapter variable namingJava ASM GeneratorAdapter 变量命名
【发布时间】:2017-08-01 15:29:57
【问题描述】:

我正在生成一个简单的类,但无法注入正确的变量名。 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);
    }

}

我使用GeneratorAdapter 来简化代码生成。由于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,并且在将调用委托给访问者链之前,它从映射中计算newIndexnewIndex 是通过另一个私有方法 remap() 计算出来的。此方法检查给定变量的映射是否已经存在,如果不存在,则创建新映射。我看到的问题是newLocal() 方法没有向映射添加任何内容。

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

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

【问题讨论】:

  • 嗯,LocalVariablesSorter目的是修改后续visit… 调用的行为。显然,所有不应该被改变的操作,都必须绕过LocalVariablesSorter,就像GeneratorAdapter引入的方法一样。
  • 那如果GeneratorAdapter不能提供父级的功能,为什么还要从LocalVariablesSorter继承呢?
  • 别问我,我不是做出这个决定的人。但是在目标访问者而不是适配器上调用该方法的问题在哪里?
  • 问题是我不使用任何其他访问者,至少是明确的。我derive 我的发电机直接来自ClassWriter。如果您能解释如何构建有效的方法访问者链,请这样做。

标签: java bytecode java-bytecode-asm


【解决方案1】:

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

因此,作为访问者 API 一部分的方法 visitLocalVariable 必须在目标 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)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-08-18
    • 2012-09-13
    • 1970-01-01
    • 2016-05-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多