Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

renaming a field using javassist at runtime in the pre-main method (java instrumentation)

I want to rename a field inside a java class at runtime. In addition, Any method that access that field ;wether it's read or write; I need it to be modified to use the new name instead of the old name....

All this will be done inside the pre-main method...

As an Exmaple, given the following code:

public class Class1
{
    String strCompany;

    public String Test()
    {
         strCompany = "TestCompany";
         return strCompany;
    }
}

In the above class, I need to change the field "strCompany" to be "strCompany2", in addition I need the method Test to use the new name instead of the old name....

changing the field name can be done using the setName method from the ctField class, but how can I modify the method body to use the new name.

like image 960
ManKeer Avatar asked Nov 04 '14 14:11

ManKeer


1 Answers

Well I'm a late on the answer but I hope you still find it useful (or at least someone else needing this kind of thing).

Even though you can use the low level bytecode api like Raphw suggested in the comment javassist does allow you to do this with the higher level API (which I recomend).

The solution I'll be presenting below will change the field name and will change all references from the old field name to the new one, which it's probably what you would want anyway since you're renaming the field.

The code

Let's use your Class1 example.

  ClassPool classpool = ClassPool.getDefault();
  CtClass ctClass = classpool.get(Class1.class.getName());
  CtField field = ctClass.getField("strCompany");
  CodeConverter codeConverter = new CodeConverter();
  codeConverter.redirectFieldAccess(field, ctClass, "strCompany2");
  ctClass.instrument(codeConverter);

  field.setName("strCompany2");
  ctClass.writeFile("./injectedClasses");

The access to CtField and setting its name I assume - due to your question - you already know how to do it. The trick about "rewiring" all field references is done using a CodeConverter that will replace all references to the CtField field for the references to the field named strCompany2 in ctClass (which happens to be the same class). Keep in mind that this needs to be done before renaming the field into strCompany2.

At the end of this run you'll have your newly Class1 in injectedClasses folder ready to use strCompany2 instead of strCompany. :-)

Sidenote

Keep in mind that what CodeConverter really does is create a new entry in the class Constant Pool and re-route all references from the entry regarding the old field to one that defines the "new" (read renamed) field.

So in the Class1 example, here is what happens:

Constant Pool BEFORE Injection

Constant pool:
#1 = Class              #2             //  test/Class1
#2 = Utf8               test/Class1
#3 = Class              #4             //  java/lang/Object
#4 = Utf8               java/lang/Object
#5 = Utf8               strCompany
#6 = Utf8               Ljava/lang/String;
#7 = Utf8               <init>
#8 = Utf8               ()V
#9 = Utf8               Code
#10 = Methodref          #3.#11         //  java/lang/Object."<init>":()V
#11 = NameAndType        #7:#8          //  "<init>":()V
#12 = Utf8               LineNumberTable
#13 = Utf8               LocalVariableTable
#14 = Utf8               this
#15 = Utf8               Ltest/Class1;
#16 = Utf8               test
#17 = Utf8               ()Ljava/lang/String;
#18 = String             #19            //  TestCompany
#19 = Utf8               TestCompany
#20 = Fieldref           #1.#21         // test/Class1.strCompany:Ljava/lang/String;
#21 = NameAndType        #5:#6          //  strCompany:Ljava/lang/String;
#22 = Utf8               SourceFile
#23 = Utf8               Class1.java

Constant pool AFTER injection

Constant pool:
#1 = Class              #2             //  test/Class1
#2 = Utf8               test/Class1
#3 = Class              #4             //  java/lang/Object
#4 = Utf8               java/lang/Object
#5 = Utf8               strCompany
#6 = Utf8               Ljava/lang/String;
#7 = Utf8               <init>
#8 = Utf8               ()V
#9 = Utf8               Code
#10 = Methodref          #3.#11         //  java/lang/Object."<init>":()V
#11 = NameAndType        #7:#8          //  "<init>":()V
#12 = Utf8               LineNumberTable
#13 = Utf8               LocalVariableTable
#14 = Utf8               this
#15 = Utf8               Ltest/Class1;
#16 = Utf8               test
#17 = Utf8               ()Ljava/lang/String;
#18 = String             #19            //  TestCompany
#19 = Utf8               TestCompany
#20 = Fieldref           #1.#21         // test/Class1.strCompany:Ljava/lang/String; 
#21 = NameAndType        #5:#6          //  strCompany:Ljava/lang/String;
#22 = Utf8               SourceFile
#23 = Utf8               Class1.java
#24 = Utf8               strCompany2
#25 = NameAndType        #24:#6         //  strCompany2:Ljava/lang/String;
#26 = Fieldref           #1.#25         //test/Class1.strCompany2:Ljava/lang/String;

In this case, with a single field rewrite your constantPool grew 3 frames which represent the definition of the new field. Usually this is not an issue, but nevertheless I rather mention it upfront.

like image 148
pabrantes Avatar answered Oct 22 '22 16:10

pabrantes