com.triage.bytecodemaster.TestObjectReferenceSwitches.java Source code

Java tutorial

Introduction

Here is the source code for com.triage.bytecodemaster.TestObjectReferenceSwitches.java

Source

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.triage.bytecodemaster;

import org.objectweb.asm.Opcodes;
import com.triage.bytecodemaster.fortesting.TestBaseClassHolder;
import com.triage.bytecodemaster.fortesting.TestPerson;
import com.triage.bytecodemaster.fortesting.TestSubClass;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.lang.reflect.Method;
import java.util.ListIterator;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.VarInsnNode;
import sun.misc.BASE64Encoder;

/**
 * The objective here is to replace an class that registers 
 * member variables with method calls that interoperate with
 * standard variables instead.
 * 
 * Basically, imagine that the code was designed with local methods that reference
 * class methods and variables.
 * 
 * We want to essentially replace those methods with equivalents that instead
 * remotely locate an instance somewhere else, and call those instead.
 * 
 * The affect is essentially in-lining a local variable for every possible
 * base class field and method reference.
 * @author dcowden
 */
public class TestObjectReferenceSwitches extends BaseWeaverTest {

    protected String GROOVY_CLASS_FIELDREF = "import com.triage.bytecodemaster.fortesting.*;\n"
            + "@groovy.transform.CompileStatic\n" + "class ScriptTestClass extends TestSubClass{\n"
            + "    void before_whatDoIThinkAbout(String x,String y){\n" +
            //INTERESTING!  return baseString actually is compiled by groovy to a call to TestBaseClass.getBaseString(),
            //since groovy allows loosey-goosey syntax.  to work aroudn this, getBaseString was changed to 
            // getBaseStringValue(). this forced groovy to use the field reference ( for the purposes of this test, anyhow)
            //weird. the bytecode generated by Groovy seems wrong. it generates referencse to an extra local variable 
            //with the line below
            //"         x = this.baseString;            \n" +
            "x = \"foo\";\n" + " System.out.println(x); \n" + "    }\n" + "}";
    protected String GROOVY_CLASS_FIELDREF2 =

            "@groovy.transform.CompileStatic\n" + "class ScriptTestClass{\n"
                    + "    void test_method(String x,String y,String z){\n" +
                    //weird. the bytecode generated by Groovy seems wrong. it generates referencse to an extra local variable 
                    //with the line below
                    //you see ASTORE 4 and ASTORE 5 for the below, even though these are invliad.
                    //it appears that the bytecodeis right if these are completely removed, however.
                    "x = \"foo\"; \n" + "}\n" + "}";

    //here we want to inject a mention from a subclass of TestSubClass (donor method)
    //into class TestFriend's WhatDoIThinkAbout method ( targetMethod )
    //the tricky part is that the donor method contains a field reference to a baseClass instance,
    //which we want to replace with a call to fetch an instance. 
    // The inline code should look like:
    //       TestBaseClass baseClass = TestBaseClassHolder.getTestBase();
    // then, the baseClass local variable should replace the field reference

    // in the script, that means that this:
    //           return baseString + x;
    // should become this:
    //           TestBaseClass baseClass = TestBaseClassHolder.getTestBase();
    //           return baseClass.baseString + x;  
    //@Test
    public void testReplacingThisWithOtherVariable() throws Exception {

        final String FIELDPROXYWORKED = "FIELDPROXYWORKED";

        //set up the proxy object. this is the object that will receive
        //the proxied calls
        TestSubClass tcc = new TestSubClass();
        tcc.setBaseString(FIELDPROXYWORKED);

        TestBaseClassHolder.setTestBase(tcc);

        //get the dynamic source that has the donor body in it
        ClassNode donorSource = loadGroovyTestClassAsBytecode(GROOVY_CLASS_FIELDREF);
        MethodNode donorMethod = findMethod(donorSource, "before_whatDoIThinkAbout");

        System.out.println("Donor");
        printMethodNode(donorMethod);

        //alright here's the strategy:  (1) inject a new local variable that points 
        //   to our remote instance,
        //  (2) inject code that sets this local to the value of a method call,
        //  (3) change references to 'this' ( ALOAD0 or ALOAD 0 ) to ALOAD1

        InsnList instructionsToInject = donorMethod.instructions;

        //make a new local variable
        LabelNode begin = new LabelNode();
        LabelNode end = new LabelNode();
        instructionsToInject.insertBefore(instructionsToInject.getFirst(), begin);
        instructionsToInject.add(end);

        Type type = Type.getObjectType("com/triage/bytecodemaster/fortesting/TestBaseClass");
        int variableIndex = donorMethod.maxLocals;
        donorMethod.maxLocals += type.getSize();
        donorMethod.visitLocalVariable("proxy", type.getDescriptor(), null, begin.getLabel(), end.getLabel(),
                variableIndex);

        //set the value of the local variable with a new instruction at the top
        //fetch a reference to our proxy object
        MethodInsnNode getTestBase = new MethodInsnNode(Opcodes.INVOKESTATIC,
                "com/triage/bytecodemaster/fortesting/TestBaseClassHolder", "getTestBase",
                "()Lcom/triage/bytecodemaster/fortesting/TestBaseClass;");

        //insert after begin label
        instructionsToInject.insert(begin, getTestBase);

        //store reference
        VarInsnNode setRef = new VarInsnNode(Opcodes.ASTORE, variableIndex);

        //insert store after fetch
        instructionsToInject.insert(getTestBase, setRef);

        //replace all references to 'this'  with the new variable

        for (int currentIndex = 0; currentIndex < instructionsToInject.size(); currentIndex++) {
            AbstractInsnNode node = instructionsToInject.get(currentIndex);
            if (node.getOpcode() == Opcodes.ALOAD) {
                VarInsnNode vin = (VarInsnNode) node;

                //'this' is var index 0. ours is var index varindex
                if (vin.var == 0) {
                    vin.var = variableIndex;
                }
            }
        }

        System.out.println(">>>>>>>>>Finished Modifying<<<<<<<<");
        printMethodNode(donorMethod);
        String NEWCLASSNAME = "ScriptTestClass";

        //write a class 
        Class c = createClassFromClassNode(donorSource, NEWCLASSNAME);

        Object o = c.newInstance();
        Method m = o.getClass().getDeclaredMethod("before_whatDoIThinkAbout", String.class);

        //should return HAHAHA not baseStringValue 
        String result = (String) m.invoke(o, new Object[] { "AAAA" });
        System.out.println("TestDonorClass.whatDoIThinkAbout Result: " + result);
        assertTrue(result.equals(FIELDPROXYWORKED + "AAAA"));
    }

    @Test
    public void writeGroovyClassFileBytes() throws Exception {
        String scriptName = "ScriptTestClass.groovy";

        String classSource = GROOVY_CLASS_FIELDREF2;
        Class groovyClass = groovyClassLoader.parseClass(classSource, scriptName);

        String className = groovyClass.getName() + ".class";
        byte[] classBytes = groovyClassLoader.getClassBytes(className);
        BASE64Encoder encoder = new BASE64Encoder();
        String rawBytes = encoder.encode(classBytes);
        FileOutputStream fos = new FileOutputStream(new File("ScriptTestClass.class"));
        fos.write(classBytes);
        fos.close();
        FileWriter fw = new FileWriter(new File("ScriptTestClass.b64"));
        fw.write(rawBytes);
        fw.close();
    }

    @Test
    public void testInjectingIntoMethodWithLotsOfParameters() throws Exception {
        final String FIELDPROXYWORKED = "FIELDPROXYWORKED";

        //set up the proxy object. this is the object that will receive
        //the proxied calls
        TestSubClass tcc = new TestSubClass();
        tcc.setBaseString(FIELDPROXYWORKED);

        TestBaseClassHolder.setTestBase(tcc);

        //get the dynamic source that has the donor body in it
        ClassNode donorSource = loadGroovyTestClassAsBytecode(GROOVY_CLASS_FIELDREF2);
        MethodNode donorMethod = findMethod(donorSource, "before_whatDoIThinkAbout");

        System.out.println("Donor Method Before Modifications:");
        printMethodNode(donorMethod);

        String TARGETCLASSNAME = "com.triage.bytecodemaster.fortesting.Concatenator";
        ClassNode targetSource = loadLocalClass(TARGETCLASSNAME);

        ClassNode exampleSource = loadLocalClass("com.triage.bytecodemaster.fortesting.JustLikeGroovyClass");
        MethodNode exampleMethod = findMethod(exampleSource, "before_whatDoIThinkAbout");

        System.out.println("Example Method-- Should be just like the Donor Source");
        printMethodNode(exampleMethod);
        MethodNode targetMethod = findMethod(targetSource, "concat");
        System.out.println("Target Method <Before Mods>");
        printMethodNode(targetMethod);

        //alright here's the strategy:  (1) inject a new local variable that points 
        //   to our remote instance,
        //  (2) inject code that sets this local to the value of a method call,
        //  (3) change references to 'this' ( ALOAD0 or ALOAD 0 ) to ALOAD1

        InsnList instructionsToInject = donorMethod.instructions;
        InsnList targetInstructions = targetMethod.instructions;

        //make a new local variable in the donor method.
        //this variable needs to have a slot high enough that it doesnt
        //conflict with either the target or the source method
        //it will hold references to the objects we replace with 'this'
        LabelNode begin = new LabelNode();
        LabelNode end = new LabelNode();
        targetInstructions.insertBefore(targetInstructions.getFirst(), begin);
        targetInstructions.add(end);

        Type type = Type.getObjectType("com/triage/bytecodemaster/fortesting/TestBaseClass");
        int variableIndex = targetMethod.maxLocals;
        targetMethod.maxLocals += type.getSize();
        targetMethod.visitLocalVariable("proxy", type.getDescriptor(), null, begin.getLabel(), end.getLabel(),
                variableIndex);

        //set the value of the local variable with a new instruction at the top
        //fetch a reference to our proxy object
        MethodInsnNode getTestBase = new MethodInsnNode(Opcodes.INVOKESTATIC,
                "com/triage/bytecodemaster/fortesting/TestBaseClassHolder", "getTestBase",
                "()Lcom/triage/bytecodemaster/fortesting/TestBaseClass;");

        //insert after begin label
        targetInstructions.insert(begin, getTestBase);

        //store reference
        VarInsnNode setRef = new VarInsnNode(Opcodes.ASTORE, variableIndex);

        //insert store after fetch
        targetInstructions.insert(getTestBase, setRef);

        //replace all references to 'this' in the DONOR method with the new variable
        //in the TARGET code

        for (int currentIndex = 0; currentIndex < instructionsToInject.size(); currentIndex++) {
            AbstractInsnNode node = instructionsToInject.get(currentIndex);
            if (node.getOpcode() == Opcodes.ALOAD) {
                VarInsnNode vin = (VarInsnNode) node;

                //'this' is var index 0. ours is var index varindex
                if (vin.var == 0) {
                    vin.var = variableIndex;
                }
            }

            //remove return methods. this will prevent a return. it should cause the donor 
            //method to have parameters that overlap with the target, which has more parameters
            if (node.getOpcode() == Opcodes.RETURN || node.getOpcode() == Opcodes.ARETURN) {
                instructionsToInject.remove(node);
            }
        }

        System.out.println(">>>>>>>>>Finished Modifying Donor Method <<<<<<<<");
        printMethodNode(donorMethod);
        String NEWCLASSNAME = "ScriptTestClass";

        //stash instructions at the beginning of the original method, 
        //but after populating the new variable
        targetInstructions.insert(setRef, instructionsToInject);

        System.out.println("Modified Target:");
        printMethodNode(targetMethod);

        //write a class 
        Class c = createClassFromClassNode(targetSource, TARGETCLASSNAME);

        Object o = c.newInstance();
        Method m = o.getClass().getDeclaredMethod("concat", String.class, String.class, String.class, String.class);

        //should return HAHAHA not baseStringValue 
        String result = (String) m.invoke(o, new Object[] { "A", "B", "C", "D" });
        System.out.println("Concatenator.concat Result: " + result);
        assertTrue(result.equals("ABCD"));

    }

    public void testMergedInReplacingThisWithOtherVariable() throws Exception {

        final String FIELDPROXYWORKED = "FIELDPROXYWORKED";

        //set up the proxy object. this is the object that will receive
        //the proxied calls
        TestSubClass tcc = new TestSubClass();
        tcc.setBaseString(FIELDPROXYWORKED);

        TestBaseClassHolder.setTestBase(tcc);

        //get the dynamic source that has the donor body in it
        ClassNode donorSource = loadGroovyTestClassAsBytecode(GROOVY_CLASS_FIELDREF);
        MethodNode donorMethod = findMethod(donorSource, "before_whatDoIThinkAbout");

        //load the target class
        String TARGETCLASSNAME = "com.triage.bytecodemaster.fortesting.TestFriend";
        ClassNode targetSource = loadLocalClass(TARGETCLASSNAME);
        MethodNode targetMethod = findMethod(targetSource, "whatDoIThinkAbout");

        System.out.println("Target");
        printMethodNode(targetMethod);

        System.out.println("Donor");
        printMethodNode(donorMethod);

        //alright here's the strategy:  (1) inject a new local variable that points 
        //   to our remote instance,
        //  (2) inject code that sets this local to the value of a method call,
        //  (3) change references to 'this' ( ALOAD0 or ALOAD 0 ) to ALOAD1

        InsnList instructionsToInject = donorMethod.instructions;

        //make a new local variable
        LabelNode begin = new LabelNode();
        LabelNode end = new LabelNode();
        instructionsToInject.insertBefore(instructionsToInject.getFirst(), begin);
        instructionsToInject.add(end);

        Type type = Type.getObjectType("com/triage/bytecodemaster/fortesting/TestBaseClass");
        int variableIndex = donorMethod.maxLocals;
        donorMethod.maxLocals += type.getSize();
        donorMethod.visitLocalVariable("proxy", type.getDescriptor(), null, begin.getLabel(), end.getLabel(),
                variableIndex);

        //set the value of the local variable with a new instruction at the top
        //fetch a reference to our proxy object
        MethodInsnNode getTestBase = new MethodInsnNode(Opcodes.INVOKESTATIC,
                "com/triage/bytecodemaster/fortesting/TestBaseClassHolder", "getTestBase",
                "()Lcom/triage/bytecodemaster/fortesting/TestBaseClass;");

        //insert after begin label
        instructionsToInject.insert(begin, getTestBase);

        //store reference
        VarInsnNode setRef = new VarInsnNode(Opcodes.ASTORE, variableIndex);

        //insert store after fetch
        instructionsToInject.insert(getTestBase, setRef);

        //replace all references to 'this'  with the new variable

        for (int currentIndex = 0; currentIndex < instructionsToInject.size(); currentIndex++) {
            AbstractInsnNode node = instructionsToInject.get(currentIndex);
            if (node.getOpcode() == Opcodes.ALOAD) {
                VarInsnNode vin = (VarInsnNode) node;

                //'this' is var index 0. ours is var index varindex
                if (vin.var == 0) {
                    vin.var = variableIndex;
                }
            }
        }

        System.out.println(">>>>>>>>>Finished Modifying<<<<<<<<");
        printMethodNode(donorMethod);

        //insert the donorMethod
        targetMethod.instructions.insert(instructionsToInject);

        System.out.println(">>>>>>>>>Final Method<<<<<<<<");
        printMethodNode(targetMethod);

        //write a class 
        Class c = createClassFromClassNode(targetSource, TARGETCLASSNAME);

        Object o = c.newInstance();
        Method m = o.getClass().getDeclaredMethod("whatDoIThinkAbout", TestPerson.class);
        TestPerson testPerson = new TestPerson();
        testPerson.setName("AAAA");
        //should return HAHAHA not baseStringValue 
        String result = (String) m.invoke(o, new Object[] { testPerson });
        System.out.println("TestFriend.whatDoIThinkAbout Result: " + result);
        assertTrue(result.equals(FIELDPROXYWORKED + "AAAA"));
    }

    /*
    mainMethod.instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"));
    mainMethod.instructions.add(new LdcInsnNode("Hello World!"));
    mainMethod.instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V"));
    mainMethod.instructions.add(new InsnNode(Opcodes.RETURN));  
            
        see ASM Type.getDescriptor() to get the descriptor for a class/object
            
        //what we want
     0: invokestatic  #2                  // Method com/triage/bytecodemaster/fortesting/TestBaseClassHolder.getTestBase:()Lcom/triage/bytecodemaster/fortesting/TestBaseClass;
     3: astore_1      
     4: aload_1       
     5: getfield      #3                  // Field com/triage/bytecodemaster/fortesting/TestBaseClass.baseString:Ljava/lang/String;
     8: astore_2       
            
        this thread helps with adding local variables
        http://osdir.com/ml/java.objectweb.asm/2007-07/msg00019.html
        http://osdir.com/ml/java.objectweb.asm/2007-07/msg00021.html
            
        */

}