User1291
User1291

Reputation: 8182

how to debug an internal error?

So I have a class Foo that should eventually adjust and reload classes. It has a method for that, too:

private void redefineClass(String classname, byte[] bytecode) {
    ClassFileLocator cfl = ClassFileLocator.Simple.of(classname,bytecode);

    Class clazz;
    try{
        clazz = Class.forName(classname);
    }catch(ClassNotFoundException e){
        throw new RuntimeException(e);
    }

    Debug._print("REDEFINING %s",clazz.getName());

    new ByteBuddy()
            .redefine(clazz,cfl)
            .make()
            .load(clazz.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent())
            ;
}

To test it, I simply load the classes from .class files to byte[] (using ASM)

private byte[] getBytecode(String classname){
    try {
        Path p = Paths.get(LayoutConstants.SRC_DIR).resolve(classname.replace(".","/") + ".class");
        File f = p.toFile();
        InputStream is = new FileInputStream(f);
        ClassReader cr = new ClassReader(is);
        ClassWriter cw = new ClassWriter(cr,0);
        cr.accept(cw,0);
        return cw.toByteArray();
    }catch(IOException e){
        throw new RuntimeException(e);
    }
}

and pass it on to redefineClass above. Seems to work for quite a few classes ... not for all, though:

REDEFINING parc.util.Vector$1
Exception in thread "Thread-0" java.lang.InternalError: Enclosing method not found
    at java.lang.Class.getEnclosingMethod(Class.java:952)
    at sun.reflect.generics.scope.ClassScope.computeEnclosingScope(ClassScope.java:50)
    at sun.reflect.generics.scope.AbstractScope.getEnclosingScope(AbstractScope.java:74)
    at sun.reflect.generics.scope.AbstractScope.lookup(AbstractScope.java:90)
    at sun.reflect.generics.factory.CoreReflectionFactory.findTypeVariable(CoreReflectionFactory.java:110)
    at sun.reflect.generics.visitor.Reifier.visitTypeVariableSignature(Reifier.java:165)
    at sun.reflect.generics.tree.TypeVariableSignature.accept(TypeVariableSignature.java:43)
    at sun.reflect.generics.visitor.Reifier.reifyTypeArguments(Reifier.java:68)
    at sun.reflect.generics.visitor.Reifier.visitClassTypeSignature(Reifier.java:138)
    at sun.reflect.generics.tree.ClassTypeSignature.accept(ClassTypeSignature.java:49)
    at sun.reflect.generics.repository.ClassRepository.getSuperInterfaces(ClassRepository.java:100)
    at java.lang.Class.getGenericInterfaces(Class.java:814)
    at net.bytebuddy.description.type.TypeList$Generic$OfLoadedInterfaceTypes$TypeProjection.resolve(TypeList.java:722)
    at net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection.accept(TypeDescription.java:5308)
    at net.bytebuddy.description.type.TypeList$Generic$AbstractBase.accept(TypeList.java:249)
    at net.bytebuddy.dynamic.scaffold.InstrumentedType$Factory$Default$1.represent(InstrumentedType.java:221)
    at net.bytebuddy.ByteBuddy.redefine(ByteBuddy.java:698)
    at net.bytebuddy.ByteBuddy.redefine(ByteBuddy.java:676)
    at parc.Foo.redefineClass(Foo.java:137)

disassembling Vector$1 gives me class Vector$1 implements java/util/Enumeration, so that indicates it's this class:

/**
 * Returns an enumeration of the components of this vector. The
 * returned {@code Enumeration} object will generate all items in
 * this vector. The first item generated is the item at index {@code 0},
 * then the item at index {@code 1}, and so on.
 *
 * @return  an enumeration of the components of this vector
 * @see     Iterator
 */
public Enumeration<E> elements() {
    return new Enumeration<E>() {
        int count = 0;

        public boolean hasMoreElements() {
            return count < elementCount;
        }

        public E nextElement() {
            synchronized (Vector.this) {
                if (count < elementCount) {
                    return elementData(count++);
                }
            }
            throw new NoSuchElementException("Vector Enumeration");
        }
    };
}

except I still have no idea what to do with that information.

For some reason the instrumented code that was saved to file can be loaded and used but can't be REloaded.

How do I find out why?

EDIT: I should mention that the project I'm working on requires Java 7.

Upvotes: 2

Views: 843

Answers (2)

Holger
Holger

Reputation: 298143

I tested several Java versions and could not find any problems with Class.getEnclosingMethod and Class.getGenericInterfaces for a local class implementing a generic interface, like in the Vector.elements()/Enumeration<E> case. Perhaps, the problems arise, because the class file has already been manipulated.

But it seems that whatever the ByteBuddy frontend is doing under the hood involving Class.getGenericInterfaces is just overkill for your use case, as you have the intended result byte code already.

I suggest going one level down and use

ClassReloadingStrategy s = ClassReloadingStrategy.fromInstalledAgent();
s.load(clazz.getClassLoader(),
    Collections.singletonMap(new TypeDescription.ForLoadedType(clazz), bytecode));

to skip these operations and just activate your byte code.

When the class loading strategy is based on ClassReloadingStrategy.Strategy.REDEFINITION you can also use

ClassReloadingStrategy s = ClassReloadingStrategy.fromInstalledAgent();
s.reset(ClassFileLocator.Simple.of(classname, bytecode), clazz);

as it will use the bytecode retrieved through the ClassFileLocator as base.

Upvotes: 1

kutschkem
kutschkem

Reputation: 8163

Looking at the byte-buddy code, I assume that ClassReloadingStrategy.fromInstalledAgent() will return a ClassReloadingStrategy configured with Strategy.REDEFINITION, which does not support anonymous classes. Use Strategy.RETRANSFORMATION instead.

ClassReloadingStrategy strat = new ClassReloadingStrategy(
   (Instrumentation) ClassLoader.getSystemClassLoader()
                    .loadClass("net.bytebuddy.agent.Installer")
                    .getMethod("getInstrumentation")
                    .invoke(null), 
   Strategy.RETRANSFORMATION);

You may consider writing a bug report, the default behavior does not match the comment which says that the default is Strategy.RETRANSFORMATION.

Upvotes: 1

Related Questions