Reputation: 8182
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
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
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