Skip to content

Instantly share code, notes, and snippets.

@alexradzin
Last active September 24, 2024 10:28
Show Gist options
  • Save alexradzin/c11bf2950f759ec48e5ebe5f27d0e348 to your computer and use it in GitHub Desktop.
Save alexradzin/c11bf2950f759ec48e5ebe5f27d0e348 to your computer and use it in GitHub Desktop.
Failing attempt to instrument base class
public class Base {
public void base() {
System.out.println("Base.base()");
}
public void foo() {
System.out.println("Base.foo()");
}
}
public class Child extends Base {
public void child() {
System.out.println("Child.child()");
}
@Override
public void foo() {
System.out.println("Child.foo()");
}
}
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