2
votes

I'm new to javassist, and started to mess with it and got some things to work. However, there are also many other things that don't seem to work.

I made a method to inject code into classes-

public static void editMethodAddEvent(CtClass target, MethodInfo method, CtClass eventClass, int start, int[] bytes, int stacksize, String constructorParameters) throws BadBytecode, NotFoundException, CannotCompileException {
    target.defrost();
    CodeAttribute codeAttribute = method.getCodeAttribute();
    CodeIterator iterator = codeAttribute.iterator();
    int classID = method.getConstPool().addClassInfo(eventClass);
    int constrnatID = method.getConstPool().addNameAndTypeInfo("<init>",constructorParameters);
    int constructID = method.getConstPool().addMethodrefInfo(classID,constrnatID);
    int callnatID = method.getConstPool().addNameAndTypeInfo("call","()V");
    int callID = method.getConstPool().addMethodrefInfo(classID,callnatID);
    iterator.insertGap(start,bytes.length);

    for (int i = 0; i < bytes.length; i++) {
        int byteCode = bytes[i];
        if (byteCode >= 0) {
            iterator.writeByte(byteCode, start+i);
        } else if (byteCode == -1) {
            iterator.writeByte(classID,start+i);
        } else if (byteCode == -2) {
            iterator.writeByte(constructID, start+i);
        } else if (byteCode == -3) {
            iterator.writeByte(callID, start+i);
        }
    }

    if(stacksize > codeAttribute.getMaxStack())
        codeAttribute.setMaxStack(stacksize);
    target.toClass();
}

I use this code to add bytecode to a method, to fire an event when the code is run.

When I compile the class (target.toClass()) at the end, it does not error(and works fine).

However, when I add a method (empty constructor in this case), using the code below, it errors.

public static Class addEmptyConstructor(Class clazz) throws NotFoundException, CannotCompileException {
    CtClass ctClass = ClassPool.getDefault().getCtClass(clazz.getName());
    ctClass.defrost();
    ClassFile classFile = ctClass.getClassFile();
    MethodInfo newMethod = new MethodInfo(classFile.getConstPool(), "<init>", "()V");
    newMethod.setCodeAttribute(new CodeAttribute(classFile.getConstPool(),1,1,new byte[]{0,0,0,0,0},new ExceptionTable(classFile.getConstPool())));
    CodeIterator iterator = newMethod.getCodeAttribute().iterator();
    iterator.writeByte(42, 0);
    iterator.writeByte(183,1);
    iterator.writeByte(0,2);
    iterator.writeByte(1,3);
    iterator.writeByte(177,4);
    classFile.addMethod(newMethod);
    return ClassPool.getDefault().toClass(ctClass);
}

The error given is:

javassist.CannotCompileException: by java.lang.LinkageError: loader (instance of  sun/misc/Launcher$AppClassLoader): attempted  duplicate class definition for name: "mod/TestClass"
        at javassist.util.proxy.DefineClassHelper.toClass2(DefineClassHelper.java:140)
        at javassist.util.proxy.DefineClassHelper.toClass(DefineClassHelper.java:95)
        at javassist.ClassPool.toClass(ClassPool.java:1143)
        at javassist.ClassPool.toClass(ClassPool.java:1106)
        at javassist.ClassPool.toClass(ClassPool.java:1064)
        at mod.edit.MethodEdit.addEmptyConstructor(MethodEdit.java:113)
        <other nonrelevant stuff...>
  Caused by: java.lang.LinkageError: loader (instance of  
  sun/misc/Launcher$AppClassLoader): attempted  duplicate class definition for 
  name: "haven/mod/TestClass"
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClass(Unknown Source)
        at java.lang.ClassLoader.defineClass(Unknown Source)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.lang.reflect.Method.invoke(Unknown Source)
        at javassist.util.proxy.DefineClassHelper.toClass3(DefineClassHelper.java:152)
        at javassist.util.proxy.DefineClassHelper.toClass2(DefineClassHelper.java:134)
        ... 10 more

My goal is to create an empty constructor so that I can create an instance of any object without putting in parameters. Specifically I have a lot of kinds of events, that have parameters. I don't want to be able to access this constructor at compile time. Thus, I try to add a constructor with javassist during runtime, and it refuses to compile. If I simply edit a method, it does not fuss, but if I add a method, it seems to refuse to do what I want. How do I solve this problem? I've been trying for over 16 hours, researching, testing different code, and can't get anything to work. Please Help!!!!

2
Are you sure you are not adding an empty constructor to a class that already have an empty constructor? - rakwaht
Yep, I'm only calling this in certain places for testing right now. The problem occurs with adding any method, through either bytecode or the simplified CtMethod or CtConstructor methods. Thanks for trying to help though! - PoisonedPorkchop
I have a constructor that has parameters and redundant code in TestClass, so that the compiler will not create the default one. - PoisonedPorkchop

2 Answers

1
votes

Probably your problem could be that you are creating a new method and not a constructor. Did you try to look here at CtNewConstructor?

I think you should try with something like my snippet of code: first check you are not trying to add a constructor to someone who already has a constructor (I just wrot a boolean value for it you should check how to do it by yourself) and you should also check that this class is not an interface.

Then just create a new constructor with the class I just linked you and add it to your class. Here a small code snippet with an example

 if (!hasDefaultConstructor && !ctClass.isInterface()) {
      CtConstructor defaultConstructor = CtNewConstructor.make("public " + ctClass.getSimpleName() + "() {}", ctClass);
      ctClass.addConstructor(defaultConstructor);
 }
0
votes

I seem to have found the root of the problem. I was passing in a class reference to my method. From class I was doing class.getName(), to put into the classPool.get(requires string) method. It seems that by creating an instance of the class, the classloader cannot reload, as an instance of the class exists. From my limited testing it seemed that even if I called the method externally via:

MethodEdit.addEmptyConstructor(TestClass.class.getName() )

that simply by calling TestClass.class (Creating a class instance), the code could not reload (Not sure if this is valid, more testing needed). I think this is why my other method that injects code into a method worked, as I was getting the CtClass and CtMethod via direct name, not getting them via class.getName(). Although I can simply call the method manually, ("mod.TestClass" as parameter instead of TestClass.class), if a solution to this is available where I can still use the class file and not get errors, please let me know! Until then I will manually type the strings of the file names.