asm [transform class by adding the getter methods]

given the following class:

import javax.net.ssl.SSLContext;

import jbet.Util.int_ptr;


public class Chocalate {

	public int ss = 9;
	
	public static void main(String[] args) {

	   Chocalate chocalate = new Chocalate();
	  
	   
       System.out.println(chocalate.ss);
	}

}

Our goal is to 1) generate getter/setter for ss field and 2) replace the chocalate.ss with chocalate.getss();

In particular, it should be as follows.

import javax.net.ssl.SSLContext;

import jbet.Util.int_ptr;


public class Chocalate {

	public int ss = 9;
	
	public static void main(String[] args) {

	   Chocalate chocalate = new Chocalate();
	  
	   
       System.out.println(chocalate.getss());
	}

	public int getss() {
		return ss;
	}

	public void setss(int ss) {
		this.ss = ss;
	}

}

For the first goal, we inject the getter/setter during the traversal of fields.

For the second goal, we replace the fieldinstruction during the traversal of methods.

Before presenting the details, we need to introduce the workflow of the transformation system.


public  Class transform(String string, final boolean resolve) throws ClassNotFoundException {
	   
		ClassReader cr =getClassReader(string);
		if(cr==null) {System.out.println("cannot read the orignal class!");   System.exit(1);}
		ClassWriter cw = new ClassWriter(0);
		ClassVisitor cv = new AddGetterVisitor(cw);
		
		cr.accept(cv, 0);
		byte[] b=cw.toByteArray();	
		
                Class c=defineClass(string, b, 0, b.length);
		
		return c;
	}

This shows the workflow of a transformation analysis: cv is a filtering wrapper upon cw.

once cr.accept is invoked, every element such as the field is traversed by cr, and delegated to the cv, cv then delegate it to the cw, with or without manipulating the classes.

Just as the java source code rewriting framework based on the AST, the delegation is adopted. During the delegation, we can do some things to change the files.


Let us take a closer look at how AddGetterVisitor achieves the two goals.

First, let see how to achieve the first goal: add the getter/setter methods.

@Override
	 public FieldVisitor visitField(
		        int access,
		        String name,
		        String desc,
		        String signature,
		        Object value)
		    {
		
	
		          FieldVisitor fv =super.visitField(access, name, desc, signature, value);
		         if((access & ACC_STATIC)==0) // we only handle the instance fields!
		         {
		          {
		        	// some additional code to make sure the getter and setter are produced for the fields!
                                  String gDesc = "()"+desc;
		        	  MethodVisitor mv = cv.visitMethod(ACC_PRIVATE, "_get"+ name, gDesc, null, null);
		        	  mv.visitCode();
		        	  Label l0 = new Label();
		        	  mv.visitLabel(l0);
		        	  mv.visitLineNumber(10, l0);
		        	  mv.visitFieldInsn(GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;");
		        	  mv.visitLdcInsn("get" + name+ " is invoked");
		        	  mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
		        	  Label l1 = new Label();
		        	  mv.visitLabel(l1);
		        	  mv.visitLineNumber(11, l1);
		        	  mv.visitVarInsn(ALOAD, 0);
		        	  mv.visitFieldInsn(GETFIELD, "Chocalate", name, desc);// change it to a general form using the arg
		        	  mv.visitInsn((IRETURN));
		        	  Label l2 = new Label();
		        	  mv.visitLabel(l2);
		        	  mv.visitLocalVariable("this", "LChocalate;", null, l0, l2, 0);
		        	  mv.visitMaxs(2, 1);
		        	  mv.visitEnd();
		          }
                          ....
		
		         }
		          
		          return fv;
		    }

Here, visitField is directly copied from the super class, ClassVisitor.

we keep the original delegation flow, that is, we allow writing the fields into the destiny class as usual.

The difference is, we inject code for getter (in the innermost bracket) method.

Do not be frightened by the code, it is actually copied from the bytecode plugin which produces ASM invocations for the expected java code.

Minor adjustments are necessary: the types and names were shown as constants by the plugin, we need to change them to the general version represented by the arguement variables.

Note that, the emitting code for setter method is not shown.


Second, let us see how to achieve the second goal, replacing the fieldget/fieldset with getter/setter.

We need to visit the method and make proper adjustment to the code emitting.

@Override //basically, we need change visitField and visitMethod.
    public MethodVisitor visitMethod(
            int access,
            String name,
            String desc,
            String signature,
            String[] exceptions)
        {
	   
		   
            MethodVisitor mv= cv.visitMethod(access, name, desc, signature, exceptions);
            if(mv==null) return null;// respect your previous decision of making it null
            else {
				mv = new AddGetterMethodAdoptor(mv);
				return mv;
			}
        
       
        }

class  AddGetterMethodAdoptor extends MethodVisitor implements Opcodes
{

	public AddGetterMethodAdoptor(MethodVisitor mvarg) {
		super(Opcodes.ASM4, mvarg);
 // DONOT MISS mvarg, it does all the dirty work of emitting code.
 // TODO Auto-generated constructor stub
	}
	
	
	@Override
    public void visitFieldInsn(int opcode, String owner, String name, String desc) {
		 
		 
		 if(owner.equals("Chocalate"))
		 {
			 if(opcode==GETFIELD)
			 {
				 String gdesc = "()"+ desc;
				 visitMethodInsn(INVOKESPECIAL, owner, "_get"+name, gdesc);
				 return ;// do not execute the normal flow of emitting the code.
			 }
			 else if (opcode==PUTFIELD)
			 {
				 String gdesc = "(" + desc+")" + "V";
				 visitMethodInsn(INVOKESPECIAL, owner, "_set"+name, gdesc);
				 return ;
			 }
		 }
		 // for other cases, we do not interfere with the normal flow, and allow the emitting of the original field instruction.
		 super.visitFieldInsn(opcode, owner, name, desc); // I do not interfere with the normal execution flow.
		
    }
	
	
	}
As seen, when we see the fieldget/fieldset instruction (field instruction), we do not delegate it to the cw, so that, no code are produced for such instruction.

Instead, we produce the method invocations using the visitMethodInsn. invokespecial is different from invokedynamic, which has multiple call targets and resorts to the runtime info of the invoker object to determine which copy to invoke.




Finally, I show the complete code.

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import javassist.bytecode.Opcode;
import kawa.lib.system;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;


public class AddGetter4Fields extends ClassLoader{


	
	public static void main(String[] args) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, ClassNotFoundException {
	  AddGetter4Fields adder = new AddGetter4Fields();
//	 Chocalate cc = new Chocalate();	 
//	  System.out.println(cc.ss);
	  Class  c = adder.transform(args[0], true); // loader instance is need to getInputStream
	  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 });
	}
 
	public  Class transform(String string, final boolean resolve) throws ClassNotFoundException {
	   if (string.startsWith("java.")) {
           System.err.println("Adapt: loading class '" + string
                   + "' without on the fly adaptation");
           return super.loadClass(string, resolve);
       } else {
           System.err.println("Adapt: loading class '" + string
                   + "' with on the fly adaptation");
       }
	   
	   
		ClassReader cr =getClassReader(string);
		if(cr==null) {System.out.println("cannot read the orignal class!");   System.exit(1);}
		ClassWriter cw = new ClassWriter(0);
		ClassVisitor cv = new AddGetterVisitor(cw);
		
		cr.accept(cv, 0);
		byte[] b ;
		b= cw.toByteArray();	
		
	   Class c=	defineClass(string, b, 0, b.length);
		
		return c;
	}

	private  ClassReader getClassReader(String name) {
		String resource = name.replace('.', '/') + ".class";
        InputStream is = getResourceAsStream(resource);
        ClassReader cr;
		try {
			cr = new ClassReader(is);
			return cr;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}        
	     return null;
	}

}


class AddGetterVisitor extends ClassVisitor implements Opcodes // implements Opcodes, for simply using the fields.
{

	public AddGetterVisitor( ClassVisitor cv) {
		super(Opcodes.ASM4, cv);		
	}

	@Override
	 public FieldVisitor visitField(
		        int access,
		        String name,
		        String desc,
		        String signature,
		        Object value)
		    {
		
	
		          FieldVisitor fv =super.visitField(access, name, desc, signature, value);
		         if((access & ACC_STATIC)==0) // we only handle the instance fields!
		         {
		          {
		        	  Type t = Type.getType(desc);
		        	  // some additional code to make sure the getter and setter are produced for the fields!
                       String gDesc = "()"+desc;
		        	  MethodVisitor mv = cv.visitMethod(ACC_PRIVATE, "_get"+ name, gDesc, null, null);
		        	  mv.visitCode();
		        	  Label l0 = new Label();
		        	  mv.visitLabel(l0);
		        	  mv.visitLineNumber(10, l0);
		        	  mv.visitFieldInsn(GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;");
		        	  mv.visitLdcInsn("get" + name+ " is invoked");
		        	  mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
		        	  Label l1 = new Label();
		        	  mv.visitLabel(l1);
		        	  mv.visitLineNumber(11, l1);
		        	  mv.visitVarInsn(ALOAD, 0);
		        	  mv.visitFieldInsn(GETFIELD, "Chocalate", name, desc);// change it to a general form using the arg
		        	  mv.visitInsn((IRETURN));
		        	  Label l2 = new Label();
		        	  mv.visitLabel(l2);
		        	  mv.visitLocalVariable("this", "LChocalate;", null, l0, l2, 0);
		        	  mv.visitMaxs(2, 1);
		        	  mv.visitEnd();
		          }
		          
		          {
		        	  Type t = Type.getType(desc);
		        	  String gdesc = "(" + desc+")" + "V";
		        	  MethodVisitor mv = cv.visitMethod(ACC_PRIVATE, "_set"+name, gdesc, null, null);
		        	  mv.visitCode();
		        	  Label l0 = new Label();
		        	  mv.visitLabel(l0);
		        	  mv.visitLineNumber(14, l0);
		        	  mv.visitFieldInsn(GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;");
		        	  mv.visitLdcInsn("set"+ name + " is invoked");
		        	  mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
		        	  Label l1 = new Label();
		        	  mv.visitLabel(l1);
		        	  mv.visitLineNumber(15, l1);
		        	  mv.visitVarInsn(ALOAD, 0);
		        	  mv.visitVarInsn(ILOAD, 1);
		        	  mv.visitFieldInsn(PUTFIELD, "Chocalate", name, desc);// more general
		        	  Label l2 = new Label();
		        	  mv.visitLabel(l2);
		        	  mv.visitLineNumber(16, l2);
		        	  mv.visitInsn(RETURN);
		        	  Label l3 = new Label();
		        	  mv.visitLabel(l3);
		        	  mv.visitLocalVariable("this", "LChocalate;", null, l0, l3, 0);
		        	  mv.visitLocalVariable("ss", "I", null, l0, l3, 1);// argument
		        	  mv.visitMaxs(2, 2);
		        	  mv.visitEnd();
		        	  
		          }
		         }
		          
		          return fv;
		    }


	
	@Override //basically, we need change visitField and visitMethod.
    public MethodVisitor visitMethod(
            int access,
            String name,
            String desc,
            String signature,
            String[] exceptions)
        {
	   
		   
            MethodVisitor mv= cv.visitMethod(access, name, desc, signature, exceptions);
            if(mv==null) return null;// respect your previous decision of making it null
            else {
				mv = new AddGetterMethodAdoptor(mv);
				return mv;
			}
        
       
        }
	

	
	}

class  AddGetterMethodAdoptor extends MethodVisitor implements Opcodes
{

	public AddGetterMethodAdoptor(MethodVisitor mvarg) {
		super(Opcodes.ASM4, mvarg);
		// TODO Auto-generated constructor stub
	}
	
	
	@Override
    public void visitFieldInsn(int opcode, String owner, String name, String desc) {
		 
		 
		 if(owner.equals("Chocalate"))
		 {
			 if(opcode==GETFIELD)
			 {
				 String gdesc = "()"+ desc;
				 visitMethodInsn(INVOKESPECIAL, owner, "_get"+name, gdesc);
				 return ;// do not execute the normal flow of emitting the code.
			 }
			 else if (opcode==PUTFIELD)
			 {
				 String gdesc = "(" + desc+")" + "V";
				 visitMethodInsn(INVOKESPECIAL, owner, "_set"+name, gdesc);
				 return ;
			 }
		 }
		 // for other cases, we do not interfere with the normal flow, and allow the emitting of the original field instruction.
		 super.visitFieldInsn(opcode, owner, name, desc); // I do not interfere with the normal execution flow.
		
    }
	
	
	}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值