I'm trying to write a java instrumentation agent using byte buddy. My goal is to replace a java standard library method call with a proxy call of my own. I was suggested to use Byte Buddy's MemberSubstitution to achieve this. I used this and this questions from SO for my reference.
I'm using Intellij IDEA for coding. My Agent code is split into multiple files as follows:
MyFirstAgent.java
public class MyFirstAgent {
public static void premain(String agentArgs, Instrumentation inst) {
new AgentBuilder.Default()
.type(ElementMatchers.any())
.transform(new ByteBuddyTransformer())
.with(AgentBuilder.Listener.StreamWriting.toSystemOut())
.with(AgentBuilder.TypeStrategy.Default.REDEFINE)
.installOn(inst);
}
ByteBuddyTransformer.java
public class ByteBuddyTransformer implements AgentBuilder.Transformer {
#Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription,
ClassLoader classLoader, JavaModule javaModule) {
try {
return builder.visit(MemberSubstitution.relaxed()
.method(named("add"))
.replaceWith(MyClass.class.getMethod("printLine"))
.on(any()));
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return builder;
}
}
MyClass.java
public class MyClass {
public boolean printLine(){
System.out.println("This is the proxy!");
return true;
}
}
And the application that I want to instrument is in another Intellij IDEA project with the following:
Main.java
public class Main {
public static void main(String[] args) {
ClassToMonitor classToMonitor = new ClassToMonitor();
classToMonitor.bar();
}
}
ClassToMonitor.java
package com.company;
import java.util.ArrayList;
import java.util.Arrays;
public class ClassToMonitor {
public void bar() {
// create an empty array list with an initial capacity
ArrayList<Integer> arrlist = new ArrayList<Integer>(5);
// use add() method to add elements in the list
arrlist.add(15);
// print all the elements available in list
for (Integer number : arrlist) {
System.out.println("Number = " + number);
}
}
}
When I build the fat jar of my agent and run it with my application, I get the following error:
[Byte Buddy] ERROR com.company.ClassToMonitor [jdk.internal.loader.ClassLoaders$AppClassLoader#2626b418, unnamed module #385e9564, loaded=false]
java.lang.IllegalStateException: Cannot invoke public boolean com.company.MyClass.printLine() on [class java.util.ArrayList, E]
I can provide the full error message if required. Also, I'm new to Java and Instrumentation in general so I might be missing something fundamental here, please kindly excuse me and point it out if that's the case.
For substitution to work, the target method needs to accept the same arguments as the replaced method, in your case an int. Also, since you are calling a member, the implicit first argument of your class needs to be the receiver type, i.e. ArrayList or any super type, even Object. Also, your replacement method needs to be static:
public class MyClass {
public static boolean printLine(Object ignored, int ignored2){
System.out.println("This is the proxy!");
return true;
}
}
MemberSubstitution is still not as flexible as it is supposed to be. You can however already inject custom byte code using the chained step if that is what you want.
Related
I am trying to add a method to an existing class at run-time with Bytebuddy, and I'm receiving no errors but not getting my expected output. This is my agent:
public class Agent {
public static void premain(String arguments, Instrumentation instrumentation) {
System.out.println("Agent for add a method ");
new AgentBuilder.Default()
.with(new AgentBuilder.InitializationStrategy.SelfInjection.Eager())
.type((ElementMatchers.nameContains("cup")))
.transform((builder, typeDescription, classLoader, module) -> builder
.defineMethod("method3", void.class, Visibility.PUBLIC)
.intercept(MethodDelegation.to(AddMethod.class))
.method(ElementMatchers.any())
.intercept(SuperMethodCall.INSTANCE
.andThen(MethodCall.invoke(ElementMatchers.nameContains("method3"))))
).installOn(instrumentation);
}
}
The expected output should be this function called:
public static void method3() throws Exception {
System.out.println("This is method 3");
}
The output from the command line when ran is just
Agent for add a method
hello world
This is the java file that I'm trying to instrument.
public class cuppy {
public static void main (String args [])
{
method1();
}
public static void method1 ()
{
System.out.println("hello world);
}
}
I'm not the brightest.
It calls the function on the invocation of an object and I wasn't ever invoking an object in my cuppy class.
Once I changed the cuppy main function to
cuppy cup = new cuppy();
cup.method1();
it did what I expected.
As for debugging such issues, always add a AgentBuilder.Listener as errors during instrumentations are always suppressed by the JVM. Glad you figured out your problem.
I'm trying to test that a class is not found with UnitTest on Android.
What's going on:
1. I'm writing an android library with transitive dependencies which are resolved in the host application
2. The developer may remove some dependencies for example remove all com.example.package
3. I have a Factory that will try to instantiate (using reflection) an Object and catch the ClassNotFoundException. If the developer remove the dependencies, the exception should be thrown.
4. I want to test this case, but all I found is issue with dependencies, not how to test for it.
Example code I want to test
try {
sNetworkResponseBuilderClass = OkHttpNetworkResponse.Builder.class;
} catch (Exception e){
// <<<< I want to test this case
new ClassNotFoundException("Unable to find OkHttpNetworkResponse.Builder.class").printStackTrace();
return null;
}
library used: hamcrast, mockito, JUnit 4.
Do you know how to do it?
So for me the first thing you need to do is to extract the part of the code that can throw a ClassNotFoundException in order to be able to easily mock it, something like:
public Class<? extends NetworkResponseBuilder> getNetworkResponseBuilderClass()
throws ClassNotFoundException {
// Your logic here
}
Then you can test a real factory instance using Mockito.spy to be able to redefine the behavior of the method getNetworkResponseBuilderClass() as next:
public void testFactoryIfNetworkResponseBuilderNotFound() {
Factory factory = spy(new Factory());
when(factory.getNetworkResponseBuilderClass()).thenThrow(
new ClassNotFoundException()
);
// The rest of your test here
}
public void testFactoryIfNetworkResponseBuilderFound() {
Factory factory = spy(new Factory());
when(factory.getNetworkResponseBuilderClass()).thenReturn(
OkHttpNetworkResponse.Builder.class
);
// The rest of your test here
}
More details about Mockito.spy.
Not quite sure if I understood your question correctly, but you can check with JUnit if an exception gets thrown:
#Test(expected=ClassNotFoundException.class)
public void testClassNotFoundException() {
// a case where the exception gets thrown
}
OkHttpNetworkResponse.Builder might be as follows:
package com.example.model;
public class OkHttpNetworkResponse {
public static class Builder {
}
}
I have a Factory that will try to instantiate (using reflection) an Object and catch the ClassNotFoundException. If the developer remove
the dependencies, the exception should be thrown.
Factory Class: which will create any object might be as follows:
package com.example.factory;
public class Factory {
public static Object getInstance(String className)
throws ClassNotFoundException, InstantiationException,
IllegalAccessException {
Class clazz = Class.forName(className);
return clazz.newInstance();
}
}
The developer may remove some dependencies for example remove all com.example.package
I want to test this case, but all I found is issue with dependencies, not how to test for it.
FactoryTest Class: which will test whether ClassNotFoundException is thrown or not might be as follows: N.B: please Check the comments carefully.
package com.example.factory;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import org.junit.Test;
public class FactoryTest {
Factory factory;
#Test(expected=ClassNotFoundException.class)
public void test() throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
ClassLoader loader = FactoryTest.class.getClassLoader();
String directory = loader.getResource(".").getPath() + "/com/example/model";
File dir = new File(directory);
//Checking directory already existed or not..
assertTrue("Directory:"+dir.getPath()+" not exist",dir.exists());
//Deleting directory
deleteDirectoryProgramatically(directory);
//Checking directory already deleted or not..
assertFalse("Directory:"+dir.getPath()+" still exist",dir.exists());
//Now getInstance Method will throw ClassNotFoundException because OkHttpNetworkResponse.Builder.class has been deleted programatically.
Factory.getInstance("OkHttpNetworkResponse.Builder.class");
}
private void deleteDirectoryProgramatically(String directory) {
File dir = new File(directory);
System.out.println(dir.getAbsolutePath());
String[] files = dir.list();
for (String f : files) {
File fl = new File(directory,f);
System.out.println(f+ " deleted?"+fl.delete());
}
System.out.println(dir+ " deleted?"+dir.delete());
}
}
It is very simple issue. JUnit4 exception unit testing is given below with an example. Hope it will clarify you.
MyNumber.java
public class MyNumber {
int number;
public MyNumber div(MyNumber rhs) {
if (rhs.number == 0) throw new IllegalArgumentException("Cannot divide by 0!");
this.number /= rhs.number;
return this;
}
}
MyNumberTest.java
public class MyNumberTest {
private MyNumber number1, number2; // Test fixtures
#Test(expected = IllegalArgumentException.class)
public void testDivByZero() {
System.out.println("Run #Test testDivByZero"); // for illustration
number2.setNumber(0);
number1.div(number2);
}
}
JUnit - Exceptions Test
To test if the code throws a desired exception, use annotation #Test(expected = exception.class), as illustrated in the previous example. For your case it will be
/**
* Check for class not found exception
**/
#Test(expected=ClassNotFoundException.class)
public void testClassNotFoundException() {
.....
}
For better understanding, you can go through this tutorial: Java Unit
Testing - JUnit & TestNG. It contains full running code example
step by step with explanation.
inside catch you can check the object with the instanceof operator as :
try {
sNetworkResponseBuilderClass = OkHttpNetworkResponse.Builder.class;
} catch (Exception e){
if(e instanceof ClassNotFoundException){
// here you can do the code you want in case of ClassNotFoundException thrown
}
}
it is your dictionary problem. in your dictionary in test class will not have . change your dictionary.
Use Class.forName("com.example.ClassName")
try {
Class.forName("com.example.OkHttpNetworkResponse.Builder");
} catch (ClassNotFoundException e) {
// This class was not found
}
See Class.forName(String className)
I was trying to modify class dynamically, such as call sleep() before a line. I attached agent to a jvm during runtime using Attach method. Then I got target class from jvm, and modified it(Add a line to call sleep()). And I got redine class error. I am using JDK1.6. I am using ASM core API to modify class.
The Error:
Caused by: java.lang.UnsupportedOperationException: class redefinition failed: attempted to change the schema (add/remove fields)
at sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
at sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:124)
Is there something wrong with ASM code? Actually my ASM code finished its job(to Add a line to call sleep()). Does current jvm not support retransform class? It seems failed to execute retransformClasses(). Does retransformClasses() not support the ASM operation(to add a line into a method to call sleep())? Any ideas? thx
EDIT:
The class which I want modify:
import java.util.concurrent.TimeUnit;
public class Person {
public String name = "abc";
public String address = "xxxxx" ;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void sayHello() throws InterruptedException {
System.out.println("aaaaaaaaaa");
System.out.println("Hello World!");
TimeUnit.SECONDS.sleep(120);
System.out.println("dd");
}
public void sayHello2() {
System.out.println("aaaaaaaaaa1");
System.out.println("Hello World!2");
}
public static void main (String args[]) {
try {
Person p = new Person();
p.sayHello(); // linenumber #9. A line to call Sleep() should be added before #here.
p.sayHello2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
My ASM code:
public void visitMethodInsn(int arg0, String arg1, String arg2, String arg3) {
Label la=new Label();
mv.visitLabel(la);
int linenumber=la.getOffset();
if(linenumber==9) {
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/util/concurrent/TimeUnit", "SECONDS", "Ljava/util/concurrent/TimeUnit;");
mv.visitLdcInsn(new Long("5"));
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/util/concurrent/TimeUnit", "sleep", "(J)V");
super.visitMethodInsn(arg0, arg1, arg2, arg3);
}
}
Not looking at your code yet, I think I can suggest something. When a class is first loaded, in addition to storing the class's byte codes, the JVM also has tables where it keeps track of the types of fields and the signatures of methods in each class.
The error you are seeing would suggest that the class was loaded, this signature information was stored and then you tried to add the method after that.
If you instead put your agent jar onto the command line, you can do things before the class is loaded for the first time. If you add your method before the signature info is stored away, you should be good.
If you have to connect the agent after the process is already launched, you may be able to transform the class but you may only be able to transform it without changing the set of fields, their types, or the methods, or their signatures. In other words, you may be able to change the byte codes but you have to not invalidate the previously-stored meta information.
We have some legacy code with Groovy, and we want to remove Groovy from the application, so, we need to get the java source code generated after using the gmaven plug-in.
Basically, in other words I am dynamically generating new classes (using gmaven Groovy maven plug in) and I would like to be able to obtain the java source code of such generated classes.
I researched a little bit and can see that the only goals for this plug in are
<goal>generateStubs</goal>
<goal>compile</goal>
<goal>generateTestStubs</goal>
<goal>testCompile</goal>
I can't see any goal that allows you to obtain the fully implemented java source code, the stub code is not enough for us as we need the final implementation source code in order to get rid of Groovy.
I'm not very familiar with the gmaven plugin, but I assume it compiles the groovy code into byte code. In this case, you can use a byte code decompiler, there is a nice list here. In the past I've used JAD and it was quite nice. The best ones will also try to create meaningful variable names based on class names.
One warning though - Groovy objects are derived from GObject, not java.lang.Object, so you would probably need to keep the groovy jar until the groovy->java porting is done. Also, be prepared that it won't be a very easy to read java...
It may be out of your scope (1 year old) but I fought against the same problem and found a method to retrieve the algorithm (not the java source code) from the decompiled groovy classes.
You may want to take a look : http://michael.laffargue.fr/blog/2013/11/02/decompiling-groovy-made-classes/
The generated stubs will be useless for you. They are just what their names suggests: stubs.
The stubs are only useful when doing joint java/groovy compilation. That's because there are two compilers involved in a java/groovy mixed project.
Parse groovy
Create stubs
Compile java and stubs (using javac)
Continue groovy compilation (using groovyc)
The groovy code will be compiled using groovyc compiler and the result is byte code.
This is an example of a generated stub:
package maba.groovy;
import java.lang.*;
import java.io.*;
import java.net.*;
import java.util.*;
import groovy.lang.*;
import groovy.util.*;
#groovy.util.logging.Log4j() public class Order
extends java.lang.Object implements
groovy.lang.GroovyObject {
public groovy.lang.MetaClass getMetaClass() { return (groovy.lang.MetaClass)null;}
public void setMetaClass(groovy.lang.MetaClass mc) { }
public java.lang.Object invokeMethod(java.lang.String method, java.lang.Object arguments) { return null;}
public java.lang.Object getProperty(java.lang.String property) { return null;}
public void setProperty(java.lang.String property, java.lang.Object value) { }
public int getPrice() { return (int)0;}
public void setPrice(int value) { }
public int getQuantity() { return (int)0;}
public void setQuantity(int value) { }
#java.lang.Override() public java.lang.String toString() { return (java.lang.String)null;}
}
As you can see there is nothing useful. And you will still depend on some groovy libraries.
This question has been on the mailing-list some time ago [0]. To summarize: Groovy to Java is hard to achieve since there are language constructs and APIs (if you do want to totally remove the Groovy dependency) that are not available in Java.
Especially with the introduction of call-site caching and other performance optimizing techniques the generated Java code would look a lot like this (for the matter of simplicity I just threw some script into JD-GUI [1]):
public class script1351632333660 extends Script
{
public script1351632333660()
{
script1351632333660 this;
CallSite[] arrayOfCallSite = $getCallSiteArray();
}
public script1351632333660(Binding arg1)
{
Binding context;
CallSite[] arrayOfCallSite = $getCallSiteArray();
ScriptBytecodeAdapter.invokeMethodOnSuperN($get$$class$groovy$lang$Script(), this, "setBinding", new Object[] { context });
}
public Object run()
{
CallSite[] arrayOfCallSite = $getCallSiteArray(); Object items = ScriptBytecodeAdapter.createList(new Object[0]);
Object[] item = (Object[])ScriptBytecodeAdapter.castToType(ScriptBytecodeAdapter.createList(new Object[] { "Fluff", arrayOfCallSite[1].callConstructor($get$$class$java$util$Date()), (Integer)DefaultTypeTransformation.box(11235813) }), $get$array$$class$java$lang$Object());
arrayOfCallSite[2].call(items, item);
arrayOfCallSite[3].callCurrent(this, items);
ValueRecorder localValueRecorder = new ValueRecorder();
try
{
Object tmp102_101 = items; localValueRecorder.record(tmp102_101, 8);
Object tmp126_121 = arrayOfCallSite[4].call(tmp102_101, new script1351632333660._run_closure1(this)); localValueRecorder.record(tmp126_121, 14); if (DefaultTypeTransformation.booleanUnbox(tmp126_121)) localValueRecorder.clear(); else ScriptBytecodeAdapter.assertFailed(AssertionRenderer.render("assert items.findAll { it }", localValueRecorder), null); } finally {
localValueRecorder.clear(); throw finally; } return null; return null; }
static { __$swapInit();
Long localLong1 = (Long)DefaultTypeTransformation.box(0L);
__timeStamp__239_neverHappen1351632333665 = localLong1.longValue();
Long localLong2 = (Long)DefaultTypeTransformation.box(1351632333665L);
__timeStamp = localLong2.longValue(); }
class _run_closure1 extends Closure implements GeneratedClosure { public _run_closure1(Object _thisObject) { super(_thisObject); }
public Object doCall(Object it) { CallSite[] arrayOfCallSite = $getCallSiteArray(); return it; return null;
}
// ...
[0] http://groovy.329449.n5.nabble.com/Java-lt-gt-Groovy-converters-td337442.html
[1] http://java.decompiler.free.fr
As many questions begin, this is driving me crazy.
I have a homegrown StarTeam java library. I have one static method like this:
public static Label getLatestDeploymentLabel(com.starbase.starteam.File child) {
// blah
}
The method works as expected when I call it from java. When I call it from Groovy, I get:
Caught: groovy.lang.MissingMethodException:
No signature of method: static pkg.starteam.StarTeamUtils.getLatestDeploymentLabel()
is applicable for argument types: (com.starbase.starteam.File)
values: [FILENAME-FOO.sql] at starteam.run(starteam.groovy:54)
I put in a println right before I call that method:
chgset.elements().each() { item ->
println "type of item is ${item.class.getName()}"
def latestlabel = StarTeamUtils.getLatestDeploymentLabel(item)
}
And confirm that, in fact, it's iterating what I expect it's iterating over:
type of item is com.starbase.starteam.File
I've seen a few different similar issues in other posts relating to static methods and the responses are along the lines of "are you sure it's a static method?". I'm sure it's a static method.
There isn't much groovy code to this. What there is of it is all contained in a single script in the default package. The main method is then called implicitly and it's in the body of the script class that the call out to the java library is made. I set the classpath in a DOS batch wrapper script, e.g.:
SET INITIALCLASSPATH=%CLASSPATH%
SET NEWCP=c:/libs/etc.jar;c:/etc/etc.jar
SET GROOVYPATH=c:/groovy.bat
SET CLASSPATH=%NEWCP%
%GROOVYPATH% %*
SET CLASSPATH=%INITIALCLASSPATH%
I created a simple situation which I think emulates my situation.
C:\apps\groovy-1.8.6\scripts>type Other.java
class Other {
private String name = "notset";
public Other(String name) {
this.name = name;
System.out.println("Created an other");
}
public String toString() {
return name;
}
}
C:\apps\groovy-1.8.6\scripts>type ThingList.java
import java.util.ArrayList;
import java.util.Iterator;
class ThingList {
ArrayList ourlist = new ArrayList<Other>();
public ThingList(){}
public ArrayList add(Other thing) {
ourlist.add(thing);
return ourlist;
}
public Iterator iterator(){
return ourlist.iterator();
}
}
C:\apps\groovy-1.8.6\scripts>type JavaLib.java
class JavaLib {
public JavaLib() {}
public static ThingList getThingList(Other thing) {
ThingList tl = new ThingList();
Other one = new Other("extra one");
tl.add(thing);
tl.add(one);
return ThingList;
}
}
C:\apps\groovy-1.8.6\scripts>type testthing.groovy
def myOther = new Other("A new other")
println "type of myOther is ${myOther.class.getName()}"
def myList = getThingList(myOther)
myList.each() {
println it
}
C:\apps\groovy-1.8.6\scripts>type wrapper.bat
#ECHO OFF
SET INITIALCLASSPATH=%CLASSPATH%
SET GROOVY=C:\apps\groovy-1.8.6\bin\groovy.bat
SET CP=.
SET CLASSPATH=%CP%
%GROOVY% %*
SET CLASSPATH=%INITIALCLASSPATH%
C:\apps\groovy-1.8.6\scripts>wrapper.bat testthing.groovy
Created an other
type of myOther is Other
Caught: groovy.lang.MissingMethodException: No signature of method: testthing.ge
tThingList() is applicable for argument types: (Other) values: [A new other]
groovy.lang.MissingMethodException: No signature of method: testthing.getThingLi
st() is applicable for argument types: (Other) values: [A new other]
at testthing.run(testthing.groovy:3)
C:\apps\groovy-1.8.6\scripts>
Any insights or suggestions would be greatly appreciated!
AndyJ
Without a way to reproduce, it's impossible to say for sure what the problem is. One possibility is that it is a class loading problem. Is the Groovy code contained in a regular Groovy class that's sitting on the class path, or does the Groovy code get loaded dynamically (e.g. by using GroovyShell)?