I'll add my code to the end of this post.
I'm using byteBuddy 1.7.9 and whatever ASM version comes with that.
In a nutshell
I have
byte[] rawClass = ...;
ClassReader cr = new ClassReader(rawClass);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
MethodAdder ma = new MethodAdder(Opcodes.ASM5,cw);
cr.accept(ma,ClassReader.EXPAND_FRAMES);
Where in MethodAdder, I want to add a static initialiser:
#Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
if(mv != null){
if(!name.equals(CLINIT_NAME)) return mv;
else{
hasStaticInitialiser = true;
return new ClinitReplacer(api,mv,classname);
}
}else return null;
}
hasStaticInitialiser = true is reached, but ClinitReplacer.visitCode is never executed.
Why?
the whole story
Let's say I want to generate class B from this example using byteBuddy.
Why bytebuddy? Well, for one it's supposedly convenient, and for another, I need its class reloading functionality.
But as you can see in the tutorial, there are some inconveniences with using "pure" byte buddy code. Most importantly,
if you really need to create byte code with jump instructions, make sure to add the correct stack map frames using ASM since Byte Buddy will not automatically include them for you.
I don't want to do that.
And even if I wanted to, I tried
builder = builder
.defineMethod("<clinit>",void.class, Modifier.STATIC)
.withParameters(new LinkedList<>())
.withoutCode()
;
and all it got me was an
Exception in thread "main" java.lang.IllegalStateException: Illegal explicit declaration of a type initializer by class B
at net.bytebuddy.dynamic.scaffold.InstrumentedType$Default.validated(InstrumentedType.java:901)
at net.bytebuddy.dynamic.scaffold.MethodRegistry$Default.prepare(MethodRegistry.java:465)
at net.bytebuddy.dynamic.scaffold.subclass.SubclassDynamicTypeBuilder.make(SubclassDynamicTypeBuilder.java:162)
at net.bytebuddy.dynamic.scaffold.subclass.SubclassDynamicTypeBuilder.make(SubclassDynamicTypeBuilder.java:155)
at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase.make(DynamicType.java:2639)
at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Delegator.make(DynamicType.java:2741)
at Main.main(Main.java)
So what I do instead is, I stop after I've added all my fields, get the bytecode for that and load the class.
Then I have ASM add the methods for me. ( In the actual application, I also need to run the bytecode through some other ASM visitors anyway.)
And then reload the re-instrumented bytecode using ByteBuddy.
The reloading fails with
Exception in thread "main" java.lang.ClassFormatError
at sun.instrument.InstrumentationImpl.redefineClasses0(Native Method)
at sun.instrument.InstrumentationImpl.redefineClasses(InstrumentationImpl.java:170)
at net.bytebuddy.dynamic.loading.ClassReloadingStrategy$Strategy$1.apply(ClassReloadingStrategy.java:261)
at net.bytebuddy.dynamic.loading.ClassReloadingStrategy.load(ClassReloadingStrategy.java:171)
at Main.main(Main.java)
And the reason for that seems to be that B looks like this when disassembled:
super public class B
extends A
version 51:0
{
public static final Field foo:"Ljava/util/Set;";
public Method "<init>":"()V"
stack 1 locals 1
{
aload_0;
invokespecial Method A."<init>":"()V";
return;
}
static Method "<clinit>":"()V";
} // end Class B
Comparing that to the rawClass bytecode, we notice that
static Method "<clinit>":"()V";
didn't exist and was indeed added by the MethodAdder.
However, the Visitor returned in
return new ClinitReplacer(api,mv,classname);
is never used. And therefore the static initialiser body remains empty resulting in the wrongful classification as native.
Code
A.java
import java.util.HashSet;
import java.util.Set;
public class A{
public static final Set foo;
static{
foo = new HashSet<String>();
foo.add("A");
}
}
Main.java
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.jar.asm.*;
import net.bytebuddy.jar.asm.commons.InstructionAdapter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
ByteBuddyAgent.install();
String targetClassname = "B";
Class superclass = A.class;
ByteBuddy byteBuddy = new ByteBuddy();
DynamicType.Builder builder = byteBuddy
.subclass(superclass)
.name(targetClassname)
;
for(Field f : superclass.getFields()){
builder = builder.defineField(f.getName(),f.getType(),f.getModifiers());
}
DynamicType.Unloaded<?> loadable = builder.make();
byte[] rawClass = loadable.getBytes();
loadable.load(A.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION);
ClassReader cr = new ClassReader(rawClass);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
MethodAdder ma = new MethodAdder(Opcodes.ASM5,cw);
cr.accept(ma,ClassReader.EXPAND_FRAMES);
byte[] finishedClass = cw.toByteArray();
Class unfinishedClass;
try {
unfinishedClass = Class.forName(targetClassname);
}catch(ClassNotFoundException e){
throw new RuntimeException(e);
}
ClassReloadingStrategy.fromInstalledAgent()
.load(
A.class.getClassLoader(),
Collections.singletonMap((TypeDescription)new TypeDescription.ForLoadedType(unfinishedClass), finishedClass)
);
Set<String> result;
try {
result = (Set<String>)Class.forName("B").getField("foo").get(null);
}catch(ClassNotFoundException | NoSuchFieldException | IllegalAccessException e){
throw new RuntimeException(e);
}
System.out.println(result);
}
private static void store(String name, byte[] finishedClass) {
Path path = Paths.get(name + ".class");
try {
FileChannel fc = null;
try {
Files.deleteIfExists(path);
fc = new FileOutputStream(path.toFile()).getChannel();
fc.write(ByteBuffer.wrap(finishedClass));
} finally {
if (fc != null) {
fc.close();
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
static class MethodAdder extends ClassVisitor implements Opcodes{
private static final String INIT_NAME = "<init>";
private static final String INIT_DESC = "()V";
private static final int CLINIT_ACCESS = ACC_STATIC;
private static final String CLINIT_NAME = "<clinit>";
private static final String CLINIT_DESC = "()V";
private static final String CLINIT_SIG = null;
private static final String[] CLINIT_EXCEPT = null;
public MethodAdder(int api, ClassVisitor cv) {
super(api, cv);
}
private String classname = null;
private boolean hasStaticInitialiser = false;
#Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
classname = name;
hasStaticInitialiser = false;
cv.visit(version, access, name, signature, superName, interfaces);
}
#Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
if(mv != null){
if(!name.equals(CLINIT_NAME)) return mv;
else{
hasStaticInitialiser = true;
return new ClinitReplacer(api,mv,classname);
}
}else return null;
}
#Override
public void visitEnd() {
if(!hasStaticInitialiser) visitMethod(CLINIT_ACCESS,CLINIT_NAME,CLINIT_DESC,CLINIT_SIG,CLINIT_EXCEPT);
if(!hasStaticInitialiser) throw new IllegalStateException("ClinitReplacer not created");
super.visitEnd();
}
private static class ClinitReplacer extends InstructionAdapter implements Opcodes{
private final String classname;
public ClinitReplacer(int api, MethodVisitor mv, String classname) {
super(api, mv);
this.classname = classname;
}
#Override
public void visitCode() {
mv.visitCode();
InstructionAdapter mv = new InstructionAdapter(this.mv);
mv.anew(Type.getType(HashSet.class));
mv.dup();
mv.dup();
mv.invokespecial(Type.getInternalName(HashSet.class),INIT_NAME,INIT_DESC,false);
mv.putstatic(classname,"foo",Type.getDescriptor(Set.class));
mv.visitLdcInsn(classname);
mv.invokevirtual(Type.getInternalName(HashSet.class),"add","(Ljava/lang/Object;)Z",false);
mv.visitInsn(RETURN);
}
}
}
}
The problem is that your source class file doesn’t have a <clinit> method, hence, ASM doesn’t invoke visitMethod at all; it is you who does in
#Override
public void visitEnd() {
if(!hasStaticInitialiser) visitMethod(CLINIT_ACCESS,CLINIT_NAME,CLINIT_DESC,CLINIT_SIG,CLINIT_EXCEPT);
if(!hasStaticInitialiser) throw new IllegalStateException("ClinitReplacer not created");
super.visitEnd();
}
Here, you are invoking visitMethod for <clinit> if you didn’t encounter it before, but you’re not doing anything with the returned MethodVisitor, hence, no-one is doing anything with it.
If you want to treat an absent <clinit> like visiting an empty initializer, to be transformed, you have to perform the appropriate method calls yourself, i.e.
#Override
public void visitEnd() {
if(!hasStaticInitialiser) {
MethodVisitor mv = visitMethod(CLINIT_ACCESS,CLINIT_NAME,CLINIT_DESC,CLINIT_SIG,CLINIT_EXCEPT);
mv.visitCode();
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
if(!hasStaticInitialiser) throw new IllegalStateException("ClinitReplacer not created");
super.visitEnd();
}
But note that then, you can’t do hot code replacement, as it doesn’t support adding any methods, including <clinit>. Further, hot code replacement won’t (re-)execute class initializers anyway.
But in your code, there is no need to load the type before performing your ASM transformation. You may remove the line
loadable.load(A.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION);
and then just use the resulting finishedClass bytecode, e.g.
ClassLoadingStrategy.Default.INJECTION.load(A.class.getClassLoader(),
Collections.singletonMap(loadable.getTypeDescription(), finishedClass));
Note that you won’t see much effect, as you are injecting code creating a HashMap, but not doing anything useful with it. You likely want to assign it to a field…
And, by the way, your code for writing a byte array is unnecessarily complicated:
private static void store(String name, byte[] finishedClass) {
Path path = Paths.get(name + ".class");
try {
FileChannel fc = null;
try {
Files.deleteIfExists(path);
fc = new FileOutputStream(path.toFile()).getChannel();
fc.write(ByteBuffer.wrap(finishedClass));
} finally {
if (fc != null) {
fc.close();
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
Just use
private static void store(String name, byte[] finishedClass) {
Path path = Paths.get(name + ".class");
try {
Files.write(path, finishedClass);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
Both, “create if it doesn’t exist” and “overwrite/truncate if it exists” are the default behavior.
To answer the part about defining a type initializer in Byte Buddy, this can be done using:
builder = builder.invokable(isTypeInitializer()).intercept(...);
You cannot explicitly define a type initializer as those initializers are for example never exposed by the reflection API and this helps to keep Byte Buddy's type description model coherent. Instead, you match the type initializer and Byte Buddy makes sure that an intializer is added as it seems appropriate.
Related
I'm writing a gradle plugin for my lib. https://github.com/shehabic/sherlock, I need to inject a network interceptor at compilation time in the byte code of OkHttp Client (https://github.com/square/okhttp/blob/master/okhttp/src/main/java/okhttp3/OkHttpClient.java)
to specific I would like to inject the following line in Java:
this.interceptors.add(new com.shehabic.sherlock.interceptors(new SherlockOkHttpInterceptor())
https://github.com/square/okhttp/blob/master/okhttp/src/main/java/okhttp3/OkHttpClient.java#L1068
I have written the plugin the transformer already and here's my class writer:
public class SherlockClassWriter {
ClassReader reader;
ClassWriter writer;
PublicizeMethodAdapter pubMethAdapter;
final static String CLASSNAME = "okhttp3.OkHttpClient";
public SherlockClassWriter() {
try {
reader = new ClassReader(CLASSNAME);
writer = new ClassWriter(reader, 0);
} catch (IOException ex) {
ex.printStackTrace();
}
}
public SherlockClassWriter(byte[] contents) {
reader = new ClassReader(contents);
writer = new ClassWriter(reader, 0);
}
public static void main(String[] args) {
SherlockClassWriter ccw = new SherlockClassWriter();
ccw.publicizeMethod();
}
public byte[] publicizeMethod() {
pubMethAdapter = new PublicizeMethodAdapter(writer);
reader.accept(pubMethAdapter, 0);
return writer.toByteArray();
}
public class PublicizeMethodAdapter extends ClassVisitor {
TraceClassVisitor tracer;
PrintWriter pw = new PrintWriter(System.out);
public PublicizeMethodAdapter(ClassVisitor cv) {
super(ASM4, cv);
this.cv = cv;
tracer = new TraceClassVisitor(cv, pw);
}
#Override
public MethodVisitor visitMethod(
int access,
String name,
String desc,
String signature,
String[] exceptions
) {
if (name.equals("build")) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
// call method in java:
// this.interceptors.add(new com.shehabic.sherlock.interceptors(new SherlockOkHttpInterceptor())
}
return tracer.visitMethod(access, name, desc, signature, exceptions);
}
}
}
a similar method that adds interceptors has a bytecode as follows:
aload_0
getfield #4 <okhttp3/OkHttpClient$Builder.interceptors>
aload_1
invokeinterface #117 <java/util/List.add> count 2
pop
aload_0
My questions are:
1.How do I inject more code into a method? even if Bytecode.
Update
Here is my working solution, based on the answer:
https://github.com/shehabic/sherlock/blob/creating-plugin-to-intercept-all-okhttp-connections/sherlock-plugin/src/main/java/com/shehabic/sherlock/plugin/SherlockClassWriter.java
There is example code to insert your line at the beggining of the function
public class YourClassVisitor extends ClassVisitor {
public YourClassVisitor(ClassVisitor cv) {
super(Opcodes.ASM5, cv);
}
#Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if (name.equals("targetName")) {
return new YourMethodVisitor(super.visitMethod(access, name, desc, signature, exceptions));
}
return super.visitMethod(access, name, desc, signature, exceptions);
}
private static class YourMethodVisitor extends MethodVisitor {
public YourMethodVisitor(MethodVisitor mv) {
super(Opcodes.ASM5, mv);
}
// This method will be called before almost all instructions
#Override
public void visitCode() {
// Default implementation is empty. So we haven't to call super method
// Puts 'this' on top of the stack. If your method is static just delete it
visitVarInsn(Opcodes.ALOAD, 0);
// Takes instance of class "the/full/name/of/your/Class" from top of the stack and put value of field interceptors
// "Ljava/util/List;" is just internal name of java.util.List
// If your field is static just replace GETFIELD with GETSTATIC
visitFieldInsn(Opcodes.GETFIELD, "the/full/name/of/your/Class", "interceptors", "Ljava/util/List;");
// Before we call add method of list we have to put target value on top of the stack
// New object creation starts with creating not initialized instance of it
visitTypeInsn(Opcodes.NEW, "com/shehabic/sherlock/interceptors");
// Than we just copy it
visitInsn(Opcodes.DUP);
visitTypeInsn(Opcodes.NEW, "example/path/to/class/SherlockOkHttpInterceptor");
visitInsn(Opcodes.DUP);
// We have to call classes constructor
// Internal name of constructor - <init>
// ()V - signature of method. () - method doesn't have parameters. V - method returns void
visitMethodInsn(Opcodes.INVOKESPECIAL, "example/path/to/class/SherlockOkHttpInterceptor", "<init>", "()V", false);
// So on top of the stack we have initialized instance of example/path/to/class/SherlockOkHttpInterceptor
// Now we can call constructor of com/shehabic/sherlock/interceptors
visitMethodInsn(Opcodes.INVOKESPECIAL, "com/shehabic/sherlock/interceptors", "<init>", "(Lexample/path/to/class/SherlockOkHttpInterceptor;)V", false);
// So on top of the stack we have initialized instance of com/shehabic/sherlock/interceptors
// Now we can put it into list
visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z", true);
}
}
}
There is example of using class visitor
byte[] cache = null;
try (FileInputStream in = new FileInputStream("C:\\Users\\JustAGod\\Projects\\gloomymods\\BuildTools\\BytecodeTools\\out\\production\\classes\\gloomyfolken\\Kek.class")) {
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassReader reader = new ClassReader(in);
reader.accept(new YourClassVisitor(writer), ClassReader.EXPAND_FRAMES);
cache = writer.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
try(FileOutputStream out = new FileOutputStream("C:\\Users\\JustAGod\\Projects\\gloomymods\\BuildTools\\BytecodeTools\\out\\production\\classes\\gloomyfolken\\Kek.class")) {
out.write(cache);
} catch (IOException e) {
e.printStackTrace();
}
I am really sorry for my English.
I want to transform a field of a class to be effectively a constant. I'm using ASM 5.0.3.
Here is the test class I have:
public class BytecodeUtilsTest {
#BeforeClass
public static void beforeClass(){
String replaceFieldClassName = "com.mypackage.ClassWithFieldToReplaceWithConstant";
String replaceFieldClassNameAsPath = replaceFieldClassName.replace('.', '/') + ".class";
// standard code to redefine class (inspired by ASM FAQ http://asm.ow2.org/doc/faq.html, sec. 5)
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
InputStream stream = contextClassLoader.getResourceAsStream(replaceFieldClassNameAsPath);
byte[] classBytes;
try {
classBytes = IOUtils.toByteArray(stream);
// here is the interesting part
byte[] patchedClassBytes = BytecodeUtils.patch(classBytes, "_fieldToReplace", true);
Reflection.invoke(contextClassLoader, "defineClass", Class.class,
new Class[]{String.class, byte[].class, int.class, int.class},
new Object[]{replaceFieldClassName, patchedClassBytes, 0, patchedClassBytes.length});
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Test
public void testFieldReplace(){
Assert.assertTrue(new ClassWithFieldToReplaceWithConstant().getFieldToReplace());
Assert.assertTrue(new ClassWithFieldToReplaceWithConstant()._fieldToReplace);
}
}
Here is the test class to update:
public class ClassWithFieldToReplaceWithConstant {
boolean _fieldToReplace;
public boolean getFieldToReplace() {
return _fieldToReplace;
}
}
And here is the patcher:
public class BytecodeUtils {
public static byte[] patch(byte[] bytecode, final String fieldToReplace, final boolean value) {
ClassReader classReader = new ClassReader(bytecode);
final ClassWriter classWriter = new ClassWriter(classReader, 0);
ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM4, classWriter) {
#Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
return new MethodVisitor(Opcodes.ASM4, super.visitMethod(access, name, desc, signature, exceptions)) {
#Override
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
if (opcode == Opcodes.GETFIELD && name.equals(fieldToReplace)) {
mv.visitInsn(Opcodes.POP);
mv.visitInsn(value ? Opcodes.ICONST_1 : Opcodes.ICONST_0);
} else {
super.visitFieldInsn(opcode, owner, name, desc);
}
}
};
}
};
classReader.accept(classVisitor, 0);
return classWriter.toByteArray();
}
}
The problem is test fails at the second assert. So if I use getter, it returns trueas expected, but if I read field directly it returns false. That's quite unexpected considering the fact that getter yields INVOKEVIRTUAL instruction and field read yields GETFIELD which is updated by invocation of visitInsn method.
What am I doing wrong and how to make direct field access return true?
For the second assert to work you need to patch BytecodeUtilsTest, not ClassWithFieldToReplaceWithConstant, since bytecode instruction to read field for the second case is actually in the BytecodeUtilsTest#testFieldReplace method.
Getter case works fine since instruction to read the field is inside the getter body, i.e, inside the ClassWithFieldToReplaceWithConstant class.
If in real scenario field is private, this code should be fine (since there will be no access to the field from outside of the class field is declared in). Otherwise, you will have to patch every class which reads or writes to this field.
I am new to ASM framework. I have been working around this ASM framework for a week. I saw tutorials in net regarding parsing a class and Generating a .class file from scratch.
But am unable to follow how to modify a existing class in ASM.
I am unable to follow the flow of execution between the ClassVisitor, ClassWriter and ClassReader.
Kindly solve my issue by giving me a ASM example for the following code.
public class ClassName {
public void showOne() {
System.out.println("Show One Method");
}
public static void main(String[] args) {
ClassName c = new ClassName();
c.showOne();
}
}
The above class should be modified as:
public class ClassName {
public void showOne() {
System.out.println("Show One Method");
}
public void showTwo() { // <- Newly added method
System.out.println("Show Two Method");
}
public static void main(String[] args) {
ClassName c = new ClassName();
c.showOne();
c.showTwo(); // <- Newly inserted method call
}
}
What should be the ASM code to modify it?
I used the ASMifier tool to generate the code. But I don't know where to apply it.
Your requirements are a bit underspecified. Below is an example program which uses ASM’s visitor API for transforming a class assumed to have the structure of your question to the resulting class. I added a convenience method taking a byte array and returning a byte array. Such a method can be used in both cases, a static transformation applied to class files on disk as well as in an Instrumentation agent.
When combining a ClassWriter with a ClassVisitor passed to a ClassReader as below, it will automatically replicate every feature of the source class so you have to override only these methods where you want to apply changes.
Here, visitMethod is overridden to intercept when encountering the main method to modify it and visitEnd is overridden to append the entirely new showTwo method. The MainTransformer will intercept RETURN instructions (there should be only one in your example) to insert the call to showTwo before it.
import org.objectweb.asm.*;
import org.objectweb.asm.commons.GeneratorAdapter;
public class MyTransformer extends ClassVisitor {
public static byte[] transform(byte[] b) {
final ClassReader classReader = new ClassReader(b);
final ClassWriter cw = new ClassWriter(classReader,
ClassWriter.COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS);
classReader.accept(new MyTransformer(cw), ClassReader.EXPAND_FRAMES);
return cw.toByteArray();
}
public MyTransformer(ClassVisitor cv) {
super(Opcodes.ASM5, cv);
}
#Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
MethodVisitor v=super.visitMethod(access, name, desc, signature, exceptions);
if(name.equals("main") && desc.equals("([Ljava/lang/String;)V"))
v=new MainTransformer(v, access, name, desc, signature, exceptions);
return v;
}
#Override
public void visitEnd() {
appendShowTwo();
super.visitEnd();
}
private void appendShowTwo() {
final MethodVisitor defVisitor = super.visitMethod(
Opcodes.ACC_PUBLIC, "showTwo", "()V", null, null);
defVisitor.visitCode();
defVisitor.visitFieldInsn(Opcodes.GETSTATIC,
"java/lang/System", "out", "Ljava/io/PrintStream;");
defVisitor.visitLdcInsn("Show Two Method");
defVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
defVisitor.visitInsn(Opcodes.RETURN);
defVisitor.visitMaxs(0, 0);
defVisitor.visitEnd();
}
class MainTransformer extends GeneratorAdapter
{
MainTransformer(MethodVisitor delegate, int access, String name, String desc,
String signature, String[] exceptions) {
super(Opcodes.ASM5, delegate, access, name, desc);
}
#Override
public void visitInsn(int opcode) {
if(opcode==Opcodes.RETURN) {
// before return insert c.showTwo();
super.visitVarInsn(Opcodes.ALOAD, 1); // variable c
super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"ClassName", "showTwo", "()V", false);
}
super.visitInsn(opcode);
}
}
}
I tried to change a classname by overriding the loadClass(String, Boolean) method.
I am creating a BukkitPlugin. The Bukkitsource can be found here
The classloader itself is working fine, i tested it and all the classes worked fine, the error started to occur after i started to change the classnames.
Here is the Method:
import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import org.bukkit.plugin.java.JavaPlugin;
public class PluginClassLoader extends URLClassLoader {
private final HashMap<String, String> replace;
public PluginClassLoader(JavaPlugin p, HashMap<String, String> replace) throws Exception {
super(new URL[0], p.getClass().getClassLoader());
this.replace = replace;
File f = null;
Field file = JavaPlugin.class.getDeclaredField("file");
file.setAccessible(true);
f = (File) file.get(p);
addURL(f.toURI().toURL());
}
private final HashMap<String, Class<?>> classes = new HashMap<>();
#Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
String s = replace.get(name);
if(s != null)
name = s;
Class<?> c;
try {
c = findClass(name);
} catch (Exception e) {
c = super.loadClass(name);
}
return c;
}
#Override
protected Class<?> loadClass(String name, boolean b)
throws ClassNotFoundException {
String s = replace.get(name);
if(s != null)
name = s;
Class<?> c;
try {
c = findClass(name);
} catch (ClassNotFoundException e) {
c = super.loadClass(name, b);
}
return c;
}
#Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
if ((name.startsWith("org.bukkit."))
|| (name.startsWith("net.minecraft."))) {
throw new ClassNotFoundException(name);
}
Class<?> result = classes.get(name);
if (result == null) {
result = super.findClass(name);
classes.put(name, result);
}
return result;
}
}
And when i use the classloader i get a NoClassDefFoundError everytime i use one of the replaced classes. My best guess is that i have to override another method, but I dont know what it could be.
The ErrorLog:
java.lang.NoClassDefFoundError: org/bukkit/craftbukkit/v1_5_R3/CraftServer
at me.leo.itemeffects.IEPlugin.onEnable(IEPlugin.java:25)
at org.bukkit.plugin.java.JavaPlugin.setEnabled(JavaPlugin.java:217)
at org.bukkit.plugin.java.JavaPluginLoader.enablePlugin(JavaPluginLoader.java:457)
at org.bukkit.plugin.SimplePluginManager.enablePlugin(SimplePluginManager.java:381)
at org.bukkit.craftbukkit.v1_6_R2.CraftServer.loadPlugin(CraftServer.java:282)
at org.bukkit.craftbukkit.v1_6_R2.CraftServer.enablePlugins(CraftServer.java:264)
at net.minecraft.server.v1_6_R2.MinecraftServer.l(MinecraftServer.java:313)
at net.minecraft.server.v1_6_R2.MinecraftServer.f(MinecraftServer.java:290)
at net.minecraft.server.v1_6_R2.MinecraftServer.a(MinecraftServer.java:250)
at net.minecraft.server.v1_6_R2.DedicatedServer.init(DedicatedServer.java:151)
at net.minecraft.server.v1_6_R2.MinecraftServer.run(MinecraftServer.java:391)
at net.minecraft.server.v1_6_R2.ThreadServerApplication.run(SourceFile:582)
IEPlugin.java:25 is this:
CraftServer server = (CraftServer) Bukkit.getServer();
The jvm is searching for the wrong classDefinition, it looks for the one with package 'v1_5_R3', but I replaced the name of the class in loadClass() with 'v1_6_R2'
I am replacing the classname 'org.bukkit.craftbukkit.v1_5_R3.CraftServer with 'org.bukkit.craftbukkit.v1_6_R2.CraftServer'
The class in the package with v1_5_R3 does not exist, that why i change it to v1_6_R2 in the loadClass(String, Boolean) Method
Thanks for reading and for your hopefully forthcoming solutions.
To start with, when you override a method it is preffered to write before it "#Override". Some programs like Eclipse, will tell you if the overriding is not corrent (if you ommited to obay some of the overriding rules)
Second of all, your class is protected, which means that it will be seen only in the package it is implemented. Are you sure the overriden class is in the same package as this one? It maybe that the reason for which you get such an error.
It is more likely to get help if you post a bigger part of your code. In that way, maybe someone will be able to tell you exactly where the problem is.
Typically ClassNotFoundException means the current class is not found and NoClassDefFoundError means a dependent class for the currently loaded class is not found (See this stackoverflow thread). Probably your replaced class has a dependency which is not present in the classpath.
I am doing project in Core Java which identifies the similarity between two files, in that one part is to identify the declared functions length. I have tried the following code to find the declared methods in a given class.
import java.lang.reflect.*;
import java.io.*;
import java.lang.String.*;
public class Method1 {
private int f1(
Object p, int x) throws NullPointerException
{
if (p == null)
throw new NullPointerException();
return x;
}
public static void main(String args[])throws Exception
{
try {
Class cls = Class.forName("Anu");
int a;
Method methlist[]= cls.getDeclaredMethods();
for (int i = 0; i < methlist.length;i++) {
Method m = methlist[i];
System.out.println(methlist[i]);
System.out.println("name = " + (m.getName()).length());
}
}
catch (Throwable e) {
System.err.println(e);
}
}
}
But i have to find all the classes for a program. Shall i give input as a program since have to identify the declared methods in each class. Secondary it is working only if the given class is compiled, ie class file exist for given class.
Can any one help me for identifying the declared methods in the given program.
And i have to identify the comment lines in the program, please help me for that too.
You need to write you program to read the original code as you cannot only find the comments there. You can parse the text yourself to find comments and method signatures.
You might be able to google for libraries wich help you do this.
Using JavaCompiler class, reading file as string and execute it as below:
public class SampleTestCase {
public static void main(String[] args) {
String str = "public class sample {public static void doSomething() {System.out.println(\"Im here\");}}";
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
SimpleJavaFileObject obj = new SourceString("sample", str);
Iterable<? extends JavaFileObject> compilationUnits = Arrays
.asList(obj);
CompilationTask task = compiler.getTask(null, null, diagnostics, null,
null, compilationUnits);
boolean success = task.call();
if (success) {
try {
Method[] declaredMethods = Class.forName("sample")
.getDeclaredMethods();
for (Method method : declaredMethods) {
System.out.println(method.getName());
}
} catch (ClassNotFoundException e) {
System.err.println("Class not found: " + e);
}
}
}
}
class SourceString extends SimpleJavaFileObject {
final String code;
SourceString(String name, String code) {
super(URI.create("string:///" + name.replace('.', '/')
+ Kind.SOURCE.extension), Kind.SOURCE);
this.code = code;
}
#Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return code;
}
}