Last active
September 24, 2024 10:28
-
-
Save alexradzin/c11bf2950f759ec48e5ebe5f27d0e348 to your computer and use it in GitHub Desktop.
Failing attempt to instrument base class
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Base { | |
public void base() { | |
System.out.println("Base.base()"); | |
} | |
public void foo() { | |
System.out.println("Base.foo()"); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Child extends Base { | |
public void child() { | |
System.out.println("Child.child()"); | |
} | |
@Override | |
public void foo() { | |
System.out.println("Child.foo()"); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import javassist.CannotCompileException; | |
import javassist.ClassPath; | |
import javassist.ClassPool; | |
import javassist.CtClass; | |
import javassist.CtMethod; | |
import javassist.LoaderClassPath; | |
import javassist.NotFoundException; | |
import org.junit.jupiter.api.Test; | |
import java.io.ByteArrayInputStream; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.lang.instrument.ClassFileTransformer; | |
import java.lang.instrument.IllegalClassFormatException; | |
import java.lang.reflect.Method; | |
import java.security.ProtectionDomain; | |
import java.util.ArrayList; | |
import java.util.Collections; | |
import java.util.List; | |
import static java.lang.String.format; | |
import static org.junit.jupiter.api.Assertions.fail; | |
public class MyTest { | |
@Test | |
void childThenBase() throws IllegalClassFormatException, IOException, ReflectiveOperationException { | |
TestClassLoader cl = new TestClassLoader(Thread.currentThread().getContextClassLoader()); | |
ClassFileTransformer transformer = new MyTransformer(); | |
byte[] bChild = instrument(cl, Child.class, transformer); | |
byte[] bBase = instrument(cl, Base.class, transformer); | |
Class<?> cChild = cl.redefineClass(Child.class.getName(), bChild); | |
Class<?> cBase = cl.redefineClass(Base.class.getName(), bBase); // throws LinkageError | |
Object instChild = cChild.getConstructor().newInstance(); | |
cChild.getMethod("foo").invoke(instChild); | |
cChild.getMethod("child").invoke(instChild); | |
cChild.getMethod("base").invoke(instChild); | |
} | |
@Test | |
void baseThenChild() throws IllegalClassFormatException, IOException, ReflectiveOperationException { | |
TestClassLoader cl = new TestClassLoader(Thread.currentThread().getContextClassLoader()); | |
ClassFileTransformer transformer = new MyTransformer(); | |
byte[] bChild = instrument(cl, Child.class, transformer); | |
byte[] bBase = instrument(cl, Base.class, transformer); | |
Class<?> cBase = cl.redefineClass(Base.class.getName(), bBase); | |
Class<?> cChild = cl.redefineClass(Child.class.getName(), bChild); | |
Object instChild = cChild.getConstructor().newInstance(); | |
cChild.getMethod("foo").invoke(instChild); | |
cChild.getMethod("child").invoke(instChild); | |
cChild.getMethod("base").invoke(instChild); | |
} | |
@Test | |
void child() throws IllegalClassFormatException, IOException, ReflectiveOperationException { | |
TestClassLoader cl = new TestClassLoader(Thread.currentThread().getContextClassLoader()); | |
ClassFileTransformer transformer = new MyTransformer(); | |
byte[] bChild = instrument(cl, Child.class, transformer); | |
Class<?> cChild = cl.redefineClass(Child.class.getName(), bChild); | |
Object instChild = cChild.getConstructor().newInstance(); | |
cChild.getMethod("foo").invoke(instChild); | |
cChild.getMethod("child").invoke(instChild); | |
cChild.getMethod("base").invoke(instChild); | |
} | |
@Test | |
void child2() throws IllegalClassFormatException, IOException, ReflectiveOperationException { | |
TestClassLoader cl = new TestClassLoader(Thread.currentThread().getContextClassLoader()); | |
ClassFileTransformer transformer = new MyTransformer2(); | |
byte[] bChild = instrument(cl, Child.class, transformer); | |
Class<?> cChild = cl.redefineClass(Child.class.getName(), bChild); | |
Object instChild = cChild.getConstructor().newInstance(); | |
// cChild.getMethod("foo").invoke(instChild); | |
// cChild.getMethod("child").invoke(instChild); | |
cChild.getMethod("base").invoke(instChild); | |
} | |
private byte[] instrument(ClassLoader cl, Class<?> clazz, ClassFileTransformer transformer) throws IllegalClassFormatException, IOException { | |
String className = clazz.getName(); | |
String classNameForTransformation = className.replace('.', '/'); | |
String resourcePath = classNameForTransformation + ".class"; | |
byte[] initialContent = null; | |
try (InputStream is = cl.getResourceAsStream(resourcePath)) { | |
if (is != null) { | |
initialContent = is.readAllBytes(); | |
} | |
} | |
if (initialContent == null) { | |
fail(format("Cannot load class %s for instrumentation", clazz)); | |
} | |
byte[] transformedContent = transformer.transform(cl, classNameForTransformation, null, clazz.getProtectionDomain(), initialContent); | |
return transformedContent; | |
} | |
private static class TestClassLoader extends ClassLoader { | |
TestClassLoader(ClassLoader parent) { | |
super(parent); | |
} | |
public Class<?> redefineClass(String name, byte[] b) throws ClassFormatError { | |
return defineClass(name, b, 0, b.length, null); | |
} | |
public boolean isClassLoaded(String classname) { | |
return findLoadedClass(classname) != null; | |
} | |
} | |
static class MyTransformer implements ClassFileTransformer { | |
@Override | |
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { | |
ClassPool pool = ClassPool.getDefault(); | |
ClassPath cp = new LoaderClassPath(Thread.currentThread().getContextClassLoader()); | |
pool.appendClassPath(cp); | |
try { | |
CtClass ctClass = pool.makeClass(new ByteArrayInputStream(classfileBuffer)); | |
for (CtMethod method : ctClass.getDeclaredMethods()) { | |
method.insertAfter("System.out.println(\"after\");"); | |
} | |
byte[] byteCode = ctClass.toBytecode(); | |
ctClass.detach(); | |
return byteCode; | |
} catch (Exception e) { | |
throw new IllegalStateException(e); | |
} | |
} | |
private void instrumentMethods2(CtClass ctClass) throws CannotCompileException, NotFoundException { | |
List<CtClass> classes = new ArrayList<>(); | |
for (CtClass c = ctClass; c != null && !Object.class.getName().equals(c.getName()); c = c.getSuperclass()) { | |
classes.add(c); | |
} | |
Collections.reverse(classes); | |
for (CtClass c : classes) { | |
for (CtMethod method : c.getDeclaredMethods()) { | |
method.insertAfter("System.out.println(\"after\");"); | |
} | |
} | |
} | |
} | |
static class MyTransformer2 implements ClassFileTransformer { | |
@Override | |
public byte[] transform(ClassLoader loader, | |
String className, | |
Class<?> classBeingRedefined, | |
ProtectionDomain protectionDomain, | |
byte[] classfileBuffer) throws IllegalClassFormatException { | |
return transform(new TestClassLoader(loader), protectionDomain, classfileBuffer); | |
} | |
private byte[] transform(TestClassLoader loader, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { | |
try { | |
ClassPool pool = ClassPool.getDefault(); | |
ClassPath cp = new LoaderClassPath(loader); | |
pool.appendClassPath(cp); | |
CtClass ctClass = pool.makeClass(new ByteArrayInputStream(classfileBuffer)); | |
List<CtClass> baseClasses = new ArrayList<>(); | |
for (CtClass c = ctClass.getSuperclass(); c != null && !Object.class.getName().equals(c.getName()); c = c.getSuperclass()) { | |
baseClasses.add(0, c); | |
} | |
for (CtClass c : baseClasses) { | |
if (!loader.isClassLoaded(c.getName())) { | |
CtClass c2 = pool.makeClass(loader.getResourceAsStream(c.getName().replace('.', '/') + ".class")); | |
loader.redefineClass(c.getName(), transform(c2)); | |
} | |
} | |
return transform(ctClass); | |
} catch (IOException | CannotCompileException | NotFoundException | RuntimeException e) { | |
IllegalClassFormatException icfe = new IllegalClassFormatException(); | |
icfe.initCause(e); | |
throw icfe; | |
} | |
} | |
private byte[] transform(CtClass ctClass) throws NotFoundException, CannotCompileException, IOException { | |
instrumentMethods(ctClass); | |
byte[] byteCode = ctClass.toBytecode(); | |
System.out.println("transforming2 " + ctClass.getName() + ": " + byteCode.length); | |
ctClass.detach(); | |
return byteCode; | |
} | |
private void instrumentMethods(CtClass ctClass) throws CannotCompileException, NotFoundException { | |
for (CtClass c = ctClass; c != null && !Object.class.getName().equals(c.getName()); c = c.getSuperclass()) { | |
for (CtMethod method : c.getDeclaredMethods()) { | |
method.insertAfter("System.out.println(\"after\");"); | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment