java - 如何使用ASM重写visitMethod将两个类合并为一个类

标签 java java-bytecode-asm bytecode-manipulation

我正在尝试通过 ASM API 在运行时将类 Callee 合并到类 Caller 。下面的部分代码是从 http://asm.ow2.org/current/asm-transformations.pdf 中的 3.1.5(将两个类合并为一个) 复制的。 。我修改了示例代码,因为我使用ASM 5.0版本。

public class Caller {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        new Caller().test("xu", "shijie");
    }

    public void test(String a, String b){
        Callee obj = new Callee(a,b);
        System.out.println(obj.calculate(10));

        System.out.println("1..........");
    }
}
public class Callee {

    final String concat;
    public Callee(String a, String b){
        concat = a+b;
    }

    public String calculate(int t){
        return concat+t;
    }

}

class MergeAdapter extends ClassVisitor {
    private ClassNode cn;
    private String cname;

    public MergeAdapter(ClassVisitor cv, ClassNode cn) {
        super(Opcodes.ASM5, cv);
        this.cn = cn;
    }

    public void visit(int version, int access, String name, String signature,
            String superName, String[] interfaces) {
        super.visit(version, access, name, signature, superName, interfaces);
        this.cname = name;
    }

    public MethodVisitor visitMethod(int access, String name, String desc,
            String signature, String[] exceptions) {
        return super.visitMethod(access, name, desc, signature, exceptions);
    }

    public void visitEnd() {
        for (Iterator<FieldNode> it = cn.fields.iterator(); it.hasNext();) {
            ((FieldNode) it.next()).accept(this);
        }
        for (Iterator it = cn.methods.iterator(); it.hasNext();) {
            MethodNode mn = (MethodNode) it.next();
            String[] exceptions = new String[mn.exceptions.size()];
            mn.exceptions.toArray(exceptions);
            MethodVisitor mv = cv.visitMethod(mn.access, mn.name, mn.desc,
                    mn.signature, exceptions);
            mn.instructions.resetLabels();
            mn.accept(new RemappingMethodAdapter(mn.access, mn.desc, mv,
                    new SimpleRemapper(cn.name, cname)));
        }
        super.visitEnd();
    }
}

public class Main extends ClassLoader{

    public byte[] generator(String caller, String callee) throws ClassNotFoundException{
        String resource = callee.replace('.', '/') + ".class";
        InputStream is =  getResourceAsStream(resource);
        byte[] buffer;
        // adapts the class on the fly
        try {
            ClassReader cr = new ClassReader(is);
            ClassNode classNode = new ClassNode();
            cr.accept(classNode, 0);

            resource = caller.replace('.', '/')+".class";
            is = getResourceAsStream(resource);
            cr = new ClassReader(is);
            ClassWriter cw = new ClassWriter(0);
            ClassVisitor visitor = new MergeAdapter(cw, classNode);
            cr.accept(visitor, 0);

            buffer= cw.toByteArray();

        } catch (Exception e) {
            throw new ClassNotFoundException(caller, e);
        }

        // optional: stores the adapted class on disk
        try {
            FileOutputStream fos = new FileOutputStream("/tmp/data.adapted");
            fos.write(buffer);
            fos.close();
        } catch (IOException e) {}
        return buffer;
     }


      @Override
      protected synchronized Class<?> loadClass(final String name,
                final boolean resolve) throws ClassNotFoundException {
            if (name.startsWith("java.")) {
                System.err.println("Adapt: loading class '" + name
                        + "' without on the fly adaptation");
                return super.loadClass(name, resolve);
            } else {
                System.err.println("Adapt: loading class '" + name
                        + "' with on the fly adaptation");
            }
            String caller = "code.sxu.asm.example.Caller";
            String callee = "code.sxu.asm.example.Callee";
            byte[] b = generator(caller, callee);
            // returns the adapted class
            return defineClass(caller, b, 0, b.length);
        }

        public static void main(final String args[]) throws Exception {
            // loads the application class (in args[0]) with an Adapt class loader
            ClassLoader loader = new Main();
            Class<?> c = loader.loadClass(args[0]);
            Method m = c.getMethod("main", new Class<?>[] { String[].class });
            String[] applicationArgs = new String[args.length - 1];
            System.arraycopy(args, 1, applicationArgs, 0, applicationArgs.length);
            m.invoke(null, new Object[] { applicationArgs });
        }
}

上面的主要问题是新创建的Callee对象

            **Callee obj = new Callee(a,b);**

Caller::test(String a, String b)的主体中仍然存在,并且为测试生成的字节码是:

public void test(java.lang.String, java.lang.String);
    flags: ACC_PUBLIC
    Code:
      stack=4, locals=4, args_size=3
         0: new           #26                 // class code/sxu/asm/example/Callee
         3: dup           
         4: aload_1       
         5: aload_2       
         6: invokespecial #28                 // Method code/sxu/asm/example/Callee."<init>":(Ljava/lang/String;Ljava/lang/String;)V
         9: astore_3      
        10: getstatic     #34                 // Field java/lang/System.out:Ljava/io/
PrintStream;

这是不正确的。因此,当 m.invoke() (重新加载)在 main 方法末尾时,这将导致“尝试重复名称的类定义:”异常。

我的想法是,Caller 类中的所有所有者:code/sxu/asm/example/Callee 也应该映射到 code/sxu/asm/example/Caller。因此我重写了

public MethodVisitor visitMethod(int access, String name, String desc,
            String signature, String[] exceptions) {
        return super.visitMethod(access, name, desc, signature, exceptions);
    }

但我不知道如何在访问方法的主体中实现这里。谁能给点建议吗?

最佳答案

public MethodVisitor visitMethod(int access, String name, String desc,
        String signature, String[] exceptions) {

    MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
    return new RemappingMethodAdapter(access, desc, mv,
                new SimpleRemapper(cn.name, cname));
}

关于java - 如何使用ASM重写visitMethod将两个类合并为一个类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29733578/

相关文章:

java - 在 Java 桌面应用程序中加载应用程序属性

带有定位的 ArrayList 中的 Java ForEach

java - ASM : how to easily get proper Opcode based on type

java - 使用 ASM 5.0 的字节码检测。注入(inject)跟踪器来跟踪局部变量

java - 如何读取CtMethod的数据

java - 如何使用 Apache POI 来证明段落的合理性?

java - 使用上下文属性的JSP include指令

java - 我可以使用相同的 "generator"和 ASM 生成 Java 字节码和源代码吗?

Java asm从方法变量中获取 "this"对象

Java 字节码操作 - 如何在方法中间注入(inject)?