Search code examples
javareflectionclassloaderbytecodejavassist

Error getMethod(java.lang.String,java.lang.Class,java.lang.Class) not found in java.lang.Class


I am stuck in Javassisst. I want to put code in the method that is located in other class. I have "no method" exception. When I just start Test2 class by itself it starts ok, without any errors. I think problem in Classloader because I am trying to invoke methods from SUTTest class in order to conduct assertions and I am trying to do it with Class1 using the same reflection (Javassist). Maybe two classloaders conflict between each other, I have no idea. How I can fix this error?

Class1 - Javassist

ClassPool pool = ClassPool.getDefault();
CtClass ctAgent = pool.get("Test2");
CtMethod method = ctAgent.getDeclaredMethod("runTestSamples");
method.insertAfter("targetClass.getMethod(\"setData\", int.class, int.class).invoke(targetInstance, 9, 2);");
//  method.insertAt(58, "memoryClassLoader.addDefinition(targetName, instrumented); 
memoryClassLoader = new MemoryClassLoader();
targetClass = memoryClassLoader.loadClass(targetName); 
targetClass.getMethod(\"setData\", int.class, int.class).invoke(targetInstance, 9, 2);");
ctAgent.toClass();
new Test2().execute();

Class2 - Test2

import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import org.jacoco.agent.AgentJar;
import org.jacoco.core.analysis.Analyzer;
import org.jacoco.core.analysis.CoverageBuilder;
import org.jacoco.core.analysis.IClassCoverage;
import org.jacoco.core.data.ExecutionDataStore;
import org.jacoco.core.data.SessionInfoStore;
import org.jacoco.core.instr.Instrumenter;
import org.jacoco.core.runtime.IRuntime;
import org.jacoco.core.runtime.LoggerRuntime;
import org.jacoco.core.runtime.RuntimeData;

public class Test2 {

    private Runnable targetInstance;
    public Class<? extends Runnable> targetClass;
    private static HashMap<Integer, String> testSamples;
    private static HashMap<String, Integer> coverageData;
    public String targetName;
    public IRuntime runtime;
    public Instrumenter instr;
    public InputStream original;
    public byte[] instrumented;
    public RuntimeData data;
    public MemoryClassLoader memoryClassLoader;

    static Test2 t2 = new Test2();
    int a;

    public static void main(String[] args) throws Exception {
        testSamples = new HashMap<Integer, String>();
        coverageData = new HashMap<String, Integer>();

        try {
            t2.execute();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void execute() throws Exception {
        testSamples = new HashMap<Integer, String>();
        coverageData = new HashMap<String, Integer>();
        targetName = SUTClass.class.getName();
        runtime = new LoggerRuntime();
        instr = new Instrumenter(runtime);
        original = getTargetClass(targetName);
        instrumented = instr.instrument(original, targetName);
        original.close();
        data = new RuntimeData();
        runtime.startup(data);
        memoryClassLoader = new MemoryClassLoader();
        memoryClassLoader.addDefinition(targetName, instrumented);
        targetClass = (Class<? extends Runnable>) memoryClassLoader.loadClass(targetName);
        targetClass.getMethod("f", int.class, int.class).invoke(targetInstance, 2, 9);
        runTestSamples(targetClass);
        targetInstance = (Runnable) targetClass.newInstance();
        // Test samples
        targetInstance.run();
        final ExecutionDataStore executionData = new ExecutionDataStore();
        final SessionInfoStore sessionInfos = new SessionInfoStore();
        data.collect(executionData, sessionInfos, false);
        runtime.shutdown();
        final CoverageBuilder coverageBuilder = new CoverageBuilder();
        final Analyzer analyzer = new Analyzer(executionData, coverageBuilder);
        original = getTargetClass(targetName);
        analyzer.analyzeClass(original, targetName);
        original.close();

        for (final IClassCoverage cc : coverageBuilder.getClasses()) {
            coverageData.put("coveredInstructions", cc.getInstructionCounter().getCoveredCount());
        }

        System.out.println(coverageData.get("coveredInstructions"));
        System.out.println(a);
    }

    public static class MemoryClassLoader extends ClassLoader {
        private final Map<String, byte[]> definitions = new HashMap<String, byte[]>();

        public void addDefinition(final String name, final byte[] bytes) {
            definitions.put(name, bytes);
        }

        @Override
        protected Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException {
            final byte[] bytes = definitions.get(name);
            if (bytes != null) {
                return defineClass(name, bytes, 0, bytes.length);
            }
            return super.loadClass(name, resolve);
        }

    }

    private InputStream getTargetClass(final String name) {
        final String resource = '/' + name.replace('.', '/') + ".class";
        return getClass().getResourceAsStream(resource);
    }

    public void runTestSamples(Class<? extends Runnable> target)
            throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException,
            SecurityException, ClassNotFoundException {
        targetClass.getMethod("f", int.class, int.class).invoke(targetInstance, 2, 9);
        // testSamples.put(1, "targetClass.getMethod(\"f\", int.class,
        // int.class).invoke(targetInstance, 2, 9)");
        // testSamples.put(2, "targetClass.getMethod(\"d\", int.class,
        // int.class).invoke(targetInstance, 2, 9)");
    }
}

Exception

    javassist.CannotCompileException: [source error] getMethod(java.lang.String,java.lang.Class,java.lang.Class) not found in java.lang.Class
    at javassist.CtBehavior.insertAfter(CtBehavior.java:909)
    at javassist.CtBehavior.insertAfter(CtBehavior.java:824)
    at Agent3$IdleBehavior.action(Agent3.java:202)
    at jade.core.behaviours.Behaviour.actionWrapper(Behaviour.java:344)
    at jade.core.Agent$ActiveLifeCycle.execute(Agent.java:1585)
    at jade.core.Agent.run(Agent.java:1524)
    at java.lang.Thread.run(Unknown Source)
Caused by: compile error: getMethod(java.lang.String,java.lang.Class,java.lang.Class) not found in java.lang.Class
    at javassist.compiler.TypeChecker.atMethodCallCore(TypeChecker.java:777)
    at javassist.compiler.TypeChecker.atCallExpr(TypeChecker.java:723)
    at javassist.compiler.JvstTypeChecker.atCallExpr(JvstTypeChecker.java:170)
    at javassist.compiler.ast.CallExpr.accept(CallExpr.java:49)
    at javassist.compiler.TypeChecker.atCallExpr(TypeChecker.java:693)
    at javassist.compiler.JvstTypeChecker.atCallExpr(JvstTypeChecker.java:170)
    at javassist.compiler.ast.CallExpr.accept(CallExpr.java:49)
    at javassist.compiler.CodeGen.doTypeCheck(CodeGen.java:266)
    at javassist.compiler.CodeGen.atStmnt(CodeGen.java:360)
    at javassist.compiler.ast.Stmnt.accept(Stmnt.java:53)
    at javassist.compiler.Javac.compileStmnt(Javac.java:578)
    at javassist.CtBehavior.insertAfterAdvice(CtBehavior.java:924)
    at javassist.CtBehavior.insertAfter(CtBehavior.java:883)
    ... 6 more

UPDATE

After the solution method.insertAfter("targetClass.getMethod(\"setData\", new Class[] { int.class, int.class }).invoke(targetInstance, new Object[] { 9, 2 });");

I have gotten new problem:

    java.lang.VerifyError: Bad type on operand stack
Exception Details:
  Location:
    Test2.runTestSamples()V @148: aastore
  Reason:
    Type integer (current frame, stack[5]) is not assignable to 'java/lang/Object'
  Current Frame:
    bci: @148
    flags: { }
    locals: { 'Test2', top, null }
    stack: { 'java/lang/reflect/Method', 'java/lang/Runnable', '[Ljava/lang/Object;', '[Ljava/lang/Object;', integer, integer }
  Bytecode:
    0x0000000: 2ab4 002e 12eb 05bd 005d 5903 b200 ed53
    0x0000010: 5904 b200 ed53 b600 f02a b400 2c05 bd00
    0x0000020: 0359 0305 b800 cd53 5904 1009 b800 cd53
    0x0000030: b600 f457 2ab4 002e 1301 3005 bd00 5d59
    0x0000040: 03b2 00ed 5359 04b2 00ed 53b6 00f0 2ab4
    0x0000050: 002c 05bd 0003 5903 1009 b800 cd53 5904
    0x0000060: 05b8 00cd 53b6 00f4 57a7 0003 014d 2ab4
    0x0000070: 002e 1301 3005 bd00 5d59 03b2 00ed 5359
    0x0000080: 04b2 00ed 53b6 00f0 2ab4 002c 05bd 0003
    0x0000090: 5903 1009 5359 0405 53b6 00f4 57b1     
  Stackmap Table:
    same_frame_extended(@108)

Solution

  • Notice that exception states

    Caused by: compile error: getMethod(java.lang.String,java.lang.Class,java.lang.Class) not found in java.lang.Class
        ...
        at javassist.CtBehavior.insertAfter(CtBehavior.java:883)
    

    According to documentation of java.lang.Class indeed there is no method with such signature, the only method with name getMethod has following signature:

    getMethod(String name, Class<?>... parameterTypes)
    

    Quoting http://www.javassist.org/tutorial/tutorial3.html#varargs :

    Currently, Javassist does not directly support varargs. ...

    public int length(int... args) { return args.length; }
    

    ... To call this method in the source code compiled by the compiler embedded in Javassist, you must write:

    length(new int[] { 1, 2, 3 });
    

    instead of this method call using the varargs mechanism:

    length(1, 2, 3);
    

    Similarly signature of method invoke from documentation of class java.lang.Method:

    invoke(Object obj, Object... args)
    

    Also quoting http://www.javassist.org/tutorial/tutorial3.html#boxing :

    Boxing and unboxing in Java are syntactic sugar. There is no bytecode for boxing or unboxing. So the compiler of Javassist does not support them. For example, the following statement is valid in Java:

    Integer i = 3;
    

    since boxing is implicitly performed. For Javassist, however, you must explicitly convert a value type from int to Integer:

    Integer i = new Integer(3);
    

    Given the above, I think that

    method.insertAfter("targetClass.getMethod(\"setData\", int.class, int.class).invoke(targetInstance, 9, 2);");
    

    should be changed on

    method.insertAfter("targetClass"
      + ".getMethod(\"setData\", new Class[] { int.class, int.class })"
      + ".invoke(targetInstance, new Object[] { new Integer(9), new Integer(2) };");