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)
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) };");