2017-08-01 61 views
1

我正在生成一個簡單的類並且無法注入一個適當的變量名稱。 ASM版本是5.2Java ASM GeneratorAdapter變量命名

下面是代碼:

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簡化代碼生成。由於GeneratorAdapterLocalVariablesSorter繼承,我假定它可以使用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()GeneratorAdapter代表visitVarInsn()改爲調用環比下跌調用的LocalVariablesSorter實施。因爲它在LocalVariablesSorter實現中,所以爲變量索引調用remap()方法並更新映射。

所以我的問題是如何使用GeneratorAdapter所以變量在發出字節碼正確命名或如何GeneratorAdapterLocalVariablesSorter鏈中,使他們正常工作結合起來?

+0

好吧,它是'LocalVariablesSorter'的*用途*,用於修改後續'visit ...'調用的行爲。顯然,所有不應該改變的操作必須繞過'LocalVariablesSorter',就像'GeneratorAdapter'引入的方法一樣。 – Holger

+0

那麼爲什麼GeneratorAdapter從LocalVariablesSorter繼承,如果它無法提供父代的功能呢? – alllex

+0

不要問我,我不是那個做出這個決定的人。但是,在目標訪問者而不是適配器上調用該方法的問題在哪裏? – Holger

回答

0

由於GeneratorAdapter延伸LocalVariablesSorter,其目的是爲了適應所有訪客呼叫,這是遊客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方法,您仍然可以使用之前的對象MethodType.getMethodDescriptor(Type.VOID_TYPE)