Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Instrument to remove (VoxxedDays Zürich 2024)

Instrument to remove (VoxxedDays Zürich 2024)

Avatar for Johannes Bechberger

Johannes Bechberger

April 21, 2024
Tweet

More Decks by Johannes Bechberger

Other Decks in Programming

Transcript

  1. web!</artifactId> !</dependency> <dependency> <groupId>org.springframework.boot!</groupId> <artifactId>spring-boot-starter- validation!</artifactId> !</dependency> <dependency> <groupId>org.springframework.boot!</groupId> <artifactId>spring-boot-starter-

    thymeleaf!</artifactId> !</dependency> <dependency> <groupId>org.springframework.boot!</groupId> <artifactId>spring-boot-starter- test!</artifactId> <scope>test!</scope> !</dependency> !<!-- Databases - Uses H2 by default --> <dependency> <groupId>com.h2database!</groupId>
  2. Software systems have a natural tendency to grow in size

    and complexity over time. A part of this growth comes with new features or bug fixes, while another part is due to useless code that accumulates over time. The problem of safely debloating real-world applications remains a long- standing software engineering endeavor today. — Soto-Valero et. al “ Source: https://arxiv.org/pdf/2008.08401.pdf
  3. So let’s write our own * as a proof of

    concept for a blog post *
  4. When are static initializers called “ • T is a

    class and an instance of A is created. • A static method declared by A is invoked. • A static field declared by A is assigned. • A static field declared by A is used and the field is not a constant variable – JLS SE17 §12.4.2. Detailed Initialization Procedure
  5. Static initializers in interfaces interface I { static { Store.getInstance().processClassUsage("I");

    } public void method(); } Forbidden in Java Allowed in Byte code
  6. Classes found in byte code Recorded classes Bloat classes Dynamically

    generated classes (e.g. for lambdas) Remove
  7. class UnusedClass { static { LOG.error("Class UnusedClass is used "

    + "which is not allowed"); } private int field; public void method() {...} }
  8. Structure JVM with Agent Instrumenter Used Classes Instrumenter JVM Instr.

    JAR Used Classes Reduced JAR JAR with Error Messages JAR Due to Spring classloader magic
  9. package java.lang; public final class String implements ... { static

    { Store.getInstance().processClassUsage("java.lang.String"); } @Stable private final byte[] value; ... public String() {...} ... }
  10. Class Loader Hierarchies platform app bootstrap X Y class reference

    delegates to searches in runtime.jar https://mostlynerdless.de/blog/2023/06/02/class-loader-hierarchies/
  11. Normally public static void main(String[] args) { ... } public

    static void main(String args[]) { ... } void main() { ... }
  12. Main Class public static void agentmain(String agentArgs) { ... }

    JVM start later attach attach public static void premain(String agentArgs) { ... }
  13. Main Class public static void agentmain(String agentArgs, Instrumentation inst) {

    ... } public static void premain(String agentArgs, Instrumentation inst) { ... }
  14. Main Class public class Main { public static void premain(String

    agentArgs, Instrumentation inst) { AgentOptions options = new AgentOptions(agentArgs); ... inst.appendToBootstrapClassLoaderSearch( new JarFile(getExtractedJARPath().toFile())); ... inst.addTransformer(new ClassTransformer(options), true); } ... } Instrumentation.retransformClasses
  15. ClassTransformer extends ClassFileTransformer “ An agent registers an implementation of

    this interface using the addTransformer method so that the transformer’s transform method is invoked when classes are loaded, redefined, or retransformed – ClassFileTransformer Documentation
  16. ClassTransformer#transform byte[] transform( ClassLoader loader, String className, Class<?> klass, ProtectionDomain

    domain, byte[] classfileBuffer) throws IllegalClassFormatException { ... }
  17. ClassTransformer#transform private void transform(String className, CtClass cc) { String cn

    = formatClassName(className); Store.getInstance() .processClassLoad(cn,cc.getClassFile().getInterfaces()); cc.makeClassInitializer() .insertBefore( String.format("me.bechberger.runtime.Store.getInstance()" + ".processClassUsage(\"%s\");", cn)); } ?
  18. With Spring-PetClinic Class org.apache.tomcat.util.net.SocketBufferHandler is used which is not allowed

    Class org.apache.tomcat.util.net.SocketBufferHandler$1 is used which is not allowed Class org.apache.tomcat.util.net.NioChannel is used which is not allowed Class org.apache.tomcat.util.net.NioChannel$1 is used which is not allowed ... java -javaagent:./target/dead-code.jar=output=classes.txt \ -jar petclinic.jar u ch.qos.logback.classic.encoder.PatternLayoutEncoder l ch.qos.logback.classic.joran.JoranConfigurator u ch.qos.logback.classic.jul.JULHelper u ch.qos.logback.classic.jul.LevelChangePropagator ...
  19. @ExtendWith(MockitoExtension.class) public class MockitoTest { @Mock List<String> mockedList; @Test public

    void whenNotUseMockAnnotation_thenCorrect() throws InterruptedException { mockedList.add("one"); Mockito.verify(mockedList).add("one"); assertEquals(0, mockedList.size()); Mockito.when(mockedList.size()).thenReturn(100); assertEquals(100, mockedList.size()); } } Thread.sleep(10000000L);
  20. public interface Collection<E> extends Iterable<E> { !// !!... default Stream<E>

    stream() { return StreamSupport.stream(this.spliterator(), false); } !//!!... }
  21. public interface Collection<E> extends Iterable<E> { !// !!... default Stream<E>

    stream() { MockMethodDispatcher var1 = MockMethodDispatcher.get("APErU6j7", this); Callable var4 = var1 !!= null !&& var1.isMocked(this) !&& !var1.isOverridden(this, Collection.class.getMethod("stream")) ? var1.handle(this, Collection.class.getMethod("stream"), new Object[0]) : null; Stream var2 = var4 !!= null ? null : StreamSupport.stream(this.spliterator(), false); if (var4 !!= null) { var2 = (Stream)var4.call(); } return var2; } !//!!... }
  22. var exprEditor = new ExprEditor() { @Override public void edit(MethodCall

    m) throws CannotCompileException { if (!isAddTransformerMethod(m)) { return; } m.replace( "me.bechberger.meta.runtime.InstrumentationHandler" + ".addTransformer($0, $1);"); } };
  23. private void transform(String className, CtClass cc) throws CannotCompileException { var

    exprEditor = 1!/* !.. !*/; for (CtConstructor constructor : cc.getDeclaredConstructors()) { constructor.instrument(exprEditor); } for (CtMethod method : cc.getDeclaredMethods()) { method.instrument(exprEditor); } }