I Know Kung Fu – Writing Java
         Bytecode



Alexander Shopov <ash@kambanaria.org>
[ash@edge ~]$ whoami
 By day: Software Engineer at Cisco
 By night: OSS contributor
 Coordinator of Bulgarian Gnome TP

 Contacts:
 E-mail:     ash@kambanaria.org
 Jabber:     al_shopov@jabber.minus273.org
 LinkedIn: http://www.linkedin.com/in/alshopov
 Web:        Just search “al_shopov”
Please Learn And Share




License: CC-BY v3.0Creative Common
Disclaimer




My opinions, knowledge and experience!
          Not my employer's.
Plan
●   What is bytecode and how to juggle it
●   Why do it, why not do it
●   How to do it
    –   Class file structure
    –   Manipulation with ASM
●   How to consume your own dog food
    –   Classloaders
    –   Proxies
    –   Agents
What it is
●   Bytecodes are the assembler of JVM. By writing new
    bytecode sequences or changing existing ones we can
    program the JVM directly – outside of the domain of the
    Java Programming Language;
●   Lower level than writing Java - JVM assembler;
●   Much easier than writing a whole (optimizing) compiler;
●   Harder than using a compiler;
●   Similar to the Jedi Dark Side – pathway to many abilities
    some consider to be unnatural.
Why do it?
●   Makes impossible things possible &
●   Makes hard things attainable &
●   Makes long tasks shorter &
●   Makes slow things faster &
●   Makes repetition go away &
●   Makes your head blow away!!!
Common use cases
●   Program analysis – find bugs, unused code
    paths, reverse engineering, metrics, etc.
●   Program generation – compilers, stub/skeleton
    compilers, fast layers without reflection.
●   Transformation – optimize,
    deoptimize/obfuscate, aspect weaving,
    performance monitoring.
Why not do it?
●   Source stops being close to executable &
●   Executable stops being close to any source &
●   There can be better or easier ways of doing things &
●   Fewer people knowing what happens means no one
    can help you &
●   Problems will be your fault! Unit test ∀ ! &
●   You take the red pill and find out the rabbit hole goes
    deep, deep, deep… EOT
Common misuse cases
●   I write it, you maintain it.
●   Do it because it is cool or others are doing it.
●   Over engineering / over generalization.
●   Solution in a search of a problem.
●   Preferring NIH to a popular solution.
Bytecode crash course in 1-2-3

   F4




                                    Thread D
                         Thread C
              Thread B
   F3                               F3
   Thread A


   F2         F2                    F2

   F1         F1         F1         F1

   F0         F0         F0         F0
Bytecode crash course in 1-2-3
                     JVM (heap)
 0     1     2 3   4   5   6 …           Class
                                 PC
               Local variables        Method code


               F0




                                          Class
                                         Pool of
                                        constants

     Stack
Bytecode crash course in 1-2-3
●   Bytecodes retrieve things from class bytestream,
    constant pool, local variables array
●   Perform computations only on stack
●   Store temporary results in local variables array
●   Enter frame by method call
●   Return from frame with a possible result (top
    thing on stack), throw top of stack
●   JVM can also throw and unroll frames
If you need more information
●   Check my other presentation:
Lifting the Veil – Reading Java Bytecode
How to do it
●   Many ways with different pluses and minuses
●   Easiest way to write byte code – use the Java
    compiler.
    –   javac – you all know that
    –   java compiler api – since 1.6
●   But more of that – wait for the end
●   Until then…
Soot
                jDec                 CGlib
 JCF Editor               cojen
               Jiapi
                   Retroweaver
JavaAssist                            BCEL
               Tea Trove
                                jReloader
   jclasslib
                                            Roo
                       Serp
      AspectJ
                                gnu.bytecode
Soot
                jDec                                CGlib
 JCF Editor                    cojen
               Jiapi One of the older frameworks,
                      Retroweaver
JavaAssist                                          BCEL
               Tea Trove
                                       jReloader
   jclasslib
                                                       Roo
                       Serp
      AspectJ
                                        gnu.bytecode
Bytecode manipulation
●   Not constrained by Java
●   Not constrained by compiler
●   Not constrained by source availability
●   Still constrained by JVM
●   Generate or change bytecode to the limits of
    JVM
Classfile Structure, 0xcafebabe
Modifiers, name, super class, interfaces
Constant pool: numeric, string and type constants
Source file name (optional)
Enclosing class reference
Annotation*
Attribute*
Inner class*     Name
Field*           Modifiers, name, type
                 Annotation*
                 Attribute*
Method*          Modifiers, name, return and parameter types
                 Annotation*
                 Attribute*
                 Compiled code
What is ASM
●   Extremely well designed bytecode
    manipulation library
●   Modular, small and fast – pick all
●   Events based – visitor (+SAX), additionally
    has tree API (DOM like)
●   Provides a small bytecode generation variant
●   Minimal – just transformation, can stack
    transformations, no classloading
Timeline
●   1.5 – 30 Aug 2004 – up to JDK 1.6 incl.
●   2.0 – 17 May 2005
●   3.0 – 01 Nov 2006
●   4.0 – 29 Oct 2011 – framework for
    compatibility
Who uses ASM?
●   Languages and AOP tools: AspectJ,
    BeanShell, CGLIB, Clojure, Groovy, Jruby,
    Jython, Coroutines
●   Tools and frameworks: Fractal, Terracotta,
    Javeleon, JRebel
●   Persistence: OpenEJB, Oracle BerkleyDB,
    EclipseLink
●   Monitoring: WebLogic, JiP
●   Testing and code analysis: Cobertura, Eclipse
Most JDKs Use It!
●   Oracle
    –   Sun – HotSpot – JDK, OpenJDK
    –   BEA/Appeal VM – JRockit
●   IBM – J9
●   Azul – Zing
    –   $JAVA_HOME/jre/lib/rt.jar
    –   com.sun.xml.internal.ws.org.objectweb.asm
    –   com.sun.xml.internal.ws.model.WrapperBeanGenerator ⇒
        RuntimeModeler ⇒ Web Services implementation
Read and explore




 Class       Class
Reader       Visitor
Read, transform, write




  Class   Class     Class     Class
 Reader   Visitor   Visitor   Writer
I know what I am doing




 Class   Class       Class     Class     Class     Class     Class     Class
Reader   Visitor     Visitor   Visitor   Visitor   Visitor   Visitor   Writer
I believe I can fly, I believe I can
                  touch the sky…
 Class     Class     Class     Class                         Class     Class
Reader     Visitor   Visitor   Visitor                       Visitor   Writer



                     Class     Class               Class     Class     Class
                     Visitor   Visitor             Visitor   Visitor   Writer



 Class     Class               Class     Class               Class     Class
Reader     Visitor             Visitor   Visitor             Visitor   Writer



           Class               Class     Class                         Class
           Visitor             Visitor   Visitor                       Writer



 Class     Class     Class               Class               Class
Reader     Visitor   Visitor             Visitor             Visitor
Basic Pattern Of Transformation
●   Read bytestream of a class
●   Generate events from that
●   Make changes to the events
●   Receive the events and serialize to
    bytestream
●   Lather, rinse, repeat
Basic diagram of class visiting
User                         ClassVisitor

             visit
         visitSource
                                            ●   Order is important
       visitOuterClass
       visitAnnotation
                                            ●   Bold return other
                         *
        visitAttribute                          visitors
       visitInnerClass
          visitFiled
                         *                  ●   * marks repeat
        vistiMethod
          visitEnd
Basic diagram of annotation visiting
 User                     AnnotationVisitor

             visit         *
           visitEnum
                                              ●   Order is important
        visitAnnotation
          visitArray
                                              ●   Bold return other
           visitEnd                               visitors
                                              ●   * marks repeat
Basic diagram of field visiting
User                         FieldVisitor

       visitAnnotation   *
        visitAttribute
                                            ●   Order is important
          visitEnd
                                            ●   Bold return other
                                                visitors
                                            ●   * marks repeat
Basic diagram of method visiting
User                               MethodVisitor

       visitAnnotationDefault
          visitAnnotation *
                                                   ●   Order is important
   visitParameterAnnotation
            visitAttribute
                                                   ●   Bold return other
              visitCode                                visitors
             visitFrame
              visitXInsn
                               *                   ●   * marks repeat
              visitLabel
         visitTryCatchBlock
          visitLocalVariable
          visitLineNumber
              visitMaxs
              visitEnd
Our humble beginning


package org.kambanaria.writebytecode.asm;
public class Zombunny { //Зомбайо

    public Integer getVersion() {
        return Integer.valueOf(1);
    }

}
Our building blocks
public class StupidClassLoader extends ClassLoader {

    private Map<String, byte[]> bytes = new HashMap<String, byte[]>();

    public StupidClassLoader() {
        super(StupidClassLoader.class.getClassLoader());
    }

    public void provide(String className, byte[] classBytes) {
        bytes.put(className, classBytes);
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        byte[] classBytes = bytes.get(name);
        Class<?> loaded;
        if (classBytes != null) {
            loaded = defineClass(name, classBytes, 0, classBytes.length);
        } else {
            ClassLoader parent = getParent();
            if (null == parent) {
                parent = ClassLoader.getSystemClassLoader();
            }
            loaded = parent.loadClass(name);
        }
        return loaded;
    }
}
Our building blocks
public final class Utilities {
    private Utilities() { }
    public static final String CLASS_NAME = "org.kambanaria.writebytecode.asm.Zombunny";
    public static final String METHOD_NAME = "getVersion";
        public static String toClassPathResourceName(String clazz) {
        return clazz.replace('.', '/') + ".class";
    }
    public static byte[] retrieveBytesFromClassPath(String className) throws IOException {
        String path = toClassPathResourceName(className);
        InputStream is = Utilities.class.getClassLoader().getResourceAsStream(path);
        InputStream classBytes = new BufferedInputStream(is);
        return is2bytes(classBytes);
    }
    public static byte[] patch(byte[] bytes, DemoClassAdapter adapter) {
        ClassReader cr = new ClassReader(bytes);
        cr.accept(adapter, ClassReader.SKIP_FRAMES);
        return adapter.getCw().toByteArray();
    }
    public static byte[] is2bytes(InputStream is) throws IOException {
        int max = 1024 * 1024;
        byte[] bytes = new byte[max]; // 1MB
        int read = is.read(bytes); // Don't do that
        byte[] result = new byte[read];
        System.arraycopy(bytes, 0, result, 0, result.length);
        return result;
    }
    public static Object call0ArgsMethodOn(Object o, String methodName) //
            throws ReflectiveOperationException {
        Class<?> c = o.getClass();
        Method m = c.getDeclaredMethod(methodName, (Class<?>[]) null);
        m.setAccessible(true);
        return m.invoke(o, (Object[]) null);
    }
}
Our building blocks

public class DemoClassAdapter extends ClassVisitor {

    public DemoClassAdapter(ClassVisitor cv) {
        super(Opcodes.ASM4, cv);
    }

    public ClassWriter getCw() {
        return (ClassWriter) cv;
    }
}
Rename class


public class Rename extends DemoClassAdapter {

    private String suffix;

    public Rename(ClassVisitor cv, String suffix) {
        super(cv);
        this.suffix = suffix;
    }

    @Override
    public void visit(int version, int access, String name, //
            String signature, String superName, String[] interfaces) {
        cv.visit(version, access, name + suffix, signature, superName, interfaces);
    }
}
Result is as if


package org.kambanaria.writebytecode.asm;
public class ZombunnySUFFIX { //Зомбайо

    public Integer getVersion() {
        return Integer.valueOf(1);
    }

}
Add field


public class AddField extends DemoClassAdapter {
    public AddField(ClassVisitor cv) {
        super(cv);
    }
   @Override
    public void visitEnd() {
        cv.visitField(ACC_PRIVATE, "_version", //
                Type.getDescriptor(Integer.class), null, null);
        cv.visitEnd();
    }
}
Add field



public class Zombunny { //Зомбайо

    private Integer _version;

    public Integer getVersion() {
        return Integer.valueOf(1);
    }

}
Change method
public class ChangeMethod extends DemoClassAdapter {
    public ChangeMethod(ClassVisitor cv) {
        super(cv);
    }
    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, //
            String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
        if (Utilities.METHOD_NAME.equals(name) && "()Ljava/lang/Integer;".equals(desc)) {
            return new DemoMethodVisitor(Opcodes.ASM4, mv);
        } else {
            return mv;
        }
    }
    class DemoMethodVisitor extends MethodVisitor {

        public DemoMethodVisitor(int version, MethodVisitor mv) {
            super(version, mv);
            mv.visitCode();
            mv.visitVarInsn(ALOAD, 0);
            mv.visitFieldInsn(GETFIELD, "org/kambanaria/writebytecode/asm/Zombunny", //
                    "_version", "Ljava/lang/Integer;");
            mv.visitInsn(ARETURN);
            mv.visitEnd();
        }
    }
}
And change method



public class Zombunny { //Зомбайо

    private Integer _version;

    public Integer getVersion() {
        return _version;
    }

}
Fix constructors
public class ManipulateConstructors extends DemoClassAdapter {
    public ManipulateConstructors(ClassVisitor cv) {
        super(cv);
    }
    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, //
            String signature, String[] exceptions) {
        if ("<init>".equals(name)) {
            MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            mv.visitCode();
            mv.visitVarInsn(ALOAD, 0);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
            mv.visitVarInsn(ALOAD, 0);
            mv.visitTypeInsn(NEW, "java/lang/Integer");
            mv.visitInsn(DUP);
            mv.visitInsn(ICONST_2);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Integer", "<init>", "(I)V");
            mv.visitFieldInsn(PUTFIELD, "org/kambanaria/writebytecode/asm/Zombunny", //
                     "_version", "Ljava/lang/Integer;");
            mv.visitInsn(RETURN);
            mv.visitMaxs(4, 1);
            mv.visitEnd();
            return mv;
        } else {
            return cv.visitMethod(access, name, desc, signature, exceptions);
        }
    }
    @Override
    public void visitEnd() {
        MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "<init>", "(Ljava/lang/Integer;)V", //
                 null, null);
        mv.visitCode();
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
        mv.visitVarInsn(ALOAD, 0);
        mv.visitVarInsn(ALOAD, 1);
        mv.visitFieldInsn(PUTFIELD, "org/kambanaria/writebytecode/asm/Zombunny", //
                 "_version", "Ljava/lang/Integer;");
        mv.visitInsn(RETURN);
        mv.visitMaxs(2, 2);
        mv.visitEnd();
        cv.visitEnd();
    }
}
And fix constructors


public class Zombunny { //Зомбайо

    private Integer _version;

    public Zombunny() {
        _version = new Integer(2);
    }

    public Zombunny(Integer version) {
        _version = version;
    }

    public Integer getVersion() {
        return _version;
    }
}
Add interface

public class AddInterface extends DemoClassAdapter {

    private static final String COMPARABLE = "java/lang/Comparable";

    public AddInterface(ClassVisitor cv) {
        super(cv);
    }

    @Override
    public void visit(int version, int access, String name, String signature,//
            String superName, String[] interfaces) {
        if (Arrays.asList(interfaces).contains(COMPARABLE)) {
            super.visit(version, access, name, signature, superName, interfaces);
        } else {
            int l = interfaces.length;
            String[] newInterfaces = new String[l + 1];
            System.arraycopy(interfaces, 0, newInterfaces, 0, l);
            newInterfaces[l] = COMPARABLE;
            super.visit(version, access, name, signature, superName, newInterfaces);
        }
    }
}
And add interface


public class Zombunny implements Comparable { //Зомбайо

    private Integer _version;

    public Zombunny() {
        _version = new Integer(2);
    }

    public Zombunny(Integer version) {
        _version = version;
    }

    public Integer getVersion() {
        return _version;
    }
}
Add interface implementation
public class AddMethod extends DemoClassAdapter {
    public AddMethod(ClassVisitor cv) {super(cv);}
    @Override
    public void visitEnd() {
        MethodVisitor compareToObject = cv.visitMethod(ACC_PUBLIC, "compareTo", //
                "(Ljava/lang/Object;)I", null, null);
        compareToObject.visitCode();
        compareToObject.visitVarInsn(ALOAD, 1);
        compareToObject.visitTypeInsn(INSTANCEOF, "org/kambanaria/writebytecode/asm/Zombunny");
        Label lbl0 = new Label();
        compareToObject.visitJumpInsn(IFNE, lbl0);
        compareToObject.visitTypeInsn(NEW, "java/lang/ClassCastException");
        compareToObject.visitInsn(DUP);
        compareToObject.visitMethodInsn(INVOKESPECIAL, "java/lang/ClassCastException", //
                "<init>", "()V");
        compareToObject.visitInsn(ATHROW);
        compareToObject.visitLabel(lbl0);
        compareToObject.visitVarInsn(ALOAD, 0);
        compareToObject.visitVarInsn(ALOAD, 1);
        compareToObject.visitTypeInsn(CHECKCAST, "org/kambanaria/writebytecode/asm/Zombunny");
        compareToObject.visitMethodInsn(INVOKEVIRTUAL, "org/kambanaria/writebytecode/asm/Zombunny", //
                "compareTo", "(Lorg/kambanaria/writebytecode/asm/Zombunny;)I");
        compareToObject.visitInsn(IRETURN);
        compareToObject.visitMaxs(2, 2);
        compareToObject.visitEnd();
        MethodVisitor compareToZombunny = cv.visitMethod(ACC_PUBLIC, "compareTo", //
                "(Lorg/kambanaria/writebytecode/asm/Zombunny;)I", null, null);
        compareToZombunny.visitCode();
        compareToZombunny.visitVarInsn(ALOAD, 0);
        compareToZombunny.visitFieldInsn(GETFIELD, "org/kambanaria/writebytecode/asm/Zombunny", //
                "_version", "Ljava/lang/Integer;");
        compareToZombunny.visitVarInsn(ALOAD, 1);
        compareToZombunny.visitFieldInsn(GETFIELD, "org/kambanaria/writebytecode/asm/Zombunny", //
                "_version", "Ljava/lang/Integer;");
        compareToZombunny.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "compareTo", //
                "(Ljava/lang/Integer;)I");
        compareToZombunny.visitInsn(IRETURN);
        compareToZombunny.visitMaxs(2, 2);
        compareToZombunny.visitEnd();
        cv.visitEnd();
    }
}
And add interface

public class Zombunny implements Comparable { //Зомбайо

    private Integer _version;

    public Zombunny() {
        _version = new Integer(2);
    }

    public Zombunny(Integer version) {
        _version = version;
    }

    public Integer getVersion() {
        return _version;
    }

    public int compareTo(Object o) {
        if (o instanceof Zombunny) {
            return compareTo((Zombunny) o);
        } else {
            throw new ClassCastException();
        }
    }

    public int compareTo(Zombunny o) {
        return _version.compareTo(o._version);
    }
}
The class we get method from



package org.kambanaria.writebytecode.asm;

public class Version {

    private Integer _version;

    public Version(Integer version) {
        _version = version;
    }

    public Integer getVersion() {
        return _version;
    }

    @Override
    public String toString() {
        return "Version: " + _version;
    }
}
Chimerization
public class Chimerize extends DemoClassAdapter {

    protected ClassNode twig;
    protected MethodNode nm;
    private static final String TWIG_NAME = "org.kambanaria.writebytecode.asm.Version";
    public Chimerize(ClassVisitor cv) throws IOException {
        super(cv);
        ClassReader rdr = new ClassReader(TWIG_NAME);
        twig = new ClassNode();
        rdr.accept(twig, 0);
        for (Object o : twig.methods) {
            MethodNode method = (MethodNode) o;
            if (method.name.equals("toString")) {
                // Guess what is missing?
                nm = method;
            }
        }
    }
    @Override
    public void visitEnd() {
        MethodVisitor chimeric =cv.visitMethod(nm.access, nm.name, nm.desc,
                nm.signature, null);
        nm.instructions.resetLabels();
        Remapper remapper = new Remapper() {
            @Override
            public String map(String name) {
                return name.replace("Version", "Zombunny");
            }
        };
        nm.accept(new RemappingMethodAdapter(nm.access, nm.desc, chimeric, remapper));
        cv.visitEnd();
    }
}
Finally
public class Zombunny implements Comparable { //Зомбайо
    private Integer _version;
    public Zombunny() {
        _version = new Integer(2);
    }
    public Zombunny(Integer version) {
        _version = version;
    }
    public Integer getVersion() {
        return _version;
    }
    public int compareTo(Object o) {
        if (o instanceof Zombunny) {
            return compareTo((Zombunny) o);
        } else {
            throw new ClassCastException();
        }
    }
    public int compareTo(Zombunny o) {
        return _version.compareTo(o._version);
    }
    @Override
    public String toString(){
         return "Version: " + _version;
    }
}
Peek at test construction chain


public class ChimerizeTest {
    Comparable sut;
    @Before
    public void setUp() throws IOException, ReflectiveOperationException {
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES + //
                ClassWriter.COMPUTE_MAXS);
        ClassVisitor cv = new AddField(new ManipulateConstructors( //
                new AddMethod(new AddInterface(new Chimerize(cw)))));
        ClassReader rdr = new ClassReader(Utilities.CLASS_NAME);
        rdr.accept(cv, 0);
        byte[] newClassBytes = cw.toByteArray();
        StupidClassLoader ldr = new StupidClassLoader();
        ldr.provide(Utilities.CLASS_NAME, newClassBytes);
        Class<?> newClass = ldr.loadClass(Utilities.CLASS_NAME);
        Constructor<?> constructor = newClass.getDeclaredConstructor(Integer.class);
        sut = (Comparable) constructor.newInstance(new Integer(42));
    }
}
Utilities
●   Type
●   Generate the visitors with: Asmfier
    –   java -classpath asm.jar:asm-util.jar:OUR_CP 
         org.objectweb.asm.util.ASMifier CLASS
●   TraceClassVisitor
●   CheckClassAdapter
Additional APIs
●   org.objectweb.asm.commons – commonly
    needed adaptors
●   org.objectweb.asm.xml – bridge to SAX 2.0,
    manipulate via XSLT, XQuery
●   org.objectweb.asm.tree – deserialize to tree
Classloaders
●   Dynamically load software components for
    Java platform
●   Lazy – loaded on demand, as late as possible
●   Type-safe linkage – must not violate type
    safety, no runtime checks
●   User-defined extensibility – normal, user
    controlled objects
●   Multiple communicating namespaces – types
    determined by class name and classloader
Classloader Chain




  Bootstrap CL        Extension CL           System CL
                                                              Application   Programmer
primordial, native    jre/lib/ext/*.jar    $CLASSPATH
                                                              classloader   classloader
    jre/lib/*.jar    -Djava.ext.dirs      -Djava.class.path
Classloader Hierarchy

                                                              Application   Programmer
                                                              classloader   classloader



  Bootstrap CL        Extension CL           System CL
                                                              Application   Programmer
primordial, native    jre/lib/ext/*.jar    $CLASSPATH
                                                              classloader   classloader
    jre/lib/*.jar    -Djava.ext.dirs      -Djava.class.path



                                                              Application   Programmer
                                                              classloader   classloader
Enterprise Classloader Hierarchy

                                                                            Programmer
                                                              Application   classloader
                                                              classloader   Programmer
                                                                            classloader

                                                                            Programmer
  Bootstrap CL        Extension CL           System CL                      classloader
                                                              Application
primordial, native    jre/lib/ext/*.jar    $CLASSPATH
                                                              classloader   Programmer
    jre/lib/*.jar    -Djava.ext.dirs      -Djava.class.path
                                                                            classloader
                                                                            Programmer
                                                              Application   classloader
                                                              classloader   Programmer
                                                                            classloader
Class Loader API

public abstract class ClassLoader {
    protected ClassLoader(ClassLoader parent);
    protected ClassLoader();

    protected Class<?> loadClass(String name, boolean resolve);
    protected Class<?> findClass(String name);
    protected final Class<?> defineClass(String name, byte[] b,
                                         int off, int len);
    protected final void resolveClass(Class<?> c);

    public URL getResource(String name);
    public Enumeration<URL> getResources(String name);

    public final MyClassLoader getParent();

    public   void   setDefaultAssertionStatus(boolean);
    public   void   setPackageAssertionStatus(String packageName,boolean enabled);
    public   void   setClassAssertionStatus(String className, boolean enabled);
    public   void   clearAssertionStatus();
}
When are class loaded?
●   Statically:
    –   Instance creation: new Integer(42);
    –   Reference to static field or method: System.out;
●   Dynamically:
    –   Class.forName("java.lang.HashMap");
    –   Class.forName("java.lang.HashMap",
                       boolean initialize,
                       ClassLoader loader);
Delegating Classloaders – Standard
Plugin or Web Classloaders
OSGI & others




 OMG!!!
Type Compatibility
●   A classloader can see and use (with exact
    type) instances of classes loaded by the
    ancestral chain and the classloader itself
●   Instances of classes loaded by sibling or
    descendant classloaders are invisible, they
    are just java.lang.Object
        Object a;
        "SomeClass".equals(a.getClass().getName());
        a instanceof Object;
        (SomeClass)a -> ClassCastException
●   Use reflection
Proxies
●   Dynamic proxy acts as a pass through/router
    to the real object
    –   runtime implementations of interfaces
    –   public, final and not abstract
    –   extend java.lang.reflect.Proxy
●   Proxy’s behaviour is determined by an
    implementation of
    java.lang.reflect.InvocationHandler
Square peg in a non square hole




      Some object
                    Some interface
Fit it with an InvocationHandler
                         Proxy




                         Object




public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;
}
public class MyInvocationHandler implements InvocationHandler {
  private Object delegate;
  public MyInvocationHandler(Object... params) {
      delegate = makeDelegate();
  }
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    Class<?>[] types = method.getParameterTypes();
    Method m = reachMethod(methodName, types);
    Object result;
    try {
        result = m.invoke(delegate, args);
    } catch (InvocationTargetException e) {
        throw e.getCause();
    }
    return result;
  }

    private static Method reachMethod(String name, Class<?>... parTypes) {
      Method m = null;
      /* Logic to determine method */
      return m;
    }

    private static Object makeDelegate(Object... params) {
      /* Produce real object instead */
      return new Object();
    }
}
Java agents
●   Package java.lang.instrument - allow Java
    programming language agents to instrument
    programs running on the JVM.
●   java ... -javaagent:jarpath[=options]
●   Manifest attributes
●   byte[] -> byte[]
Almost Real Example
Action takes place here
A planet, an icy atmosphere
This country
Outsources to this country
They deliver cheaper than the world,
  faster than the speed of thought
The entrepreneurial spirit of that
           country
Makes them sell the software to this
            country
Where it fails spectacularly while
clearly working in the other two ones
Investigation takes place
Finds the culprit

SimpleDateFormat frmt =
      new SimpleDateFormat("E MM/dd/yyyy");




SimpleDateFormat
publicSimpleDateFormat(String pattern)
Constructs a SimpleDateFormat using the
given pattern and the default date format
symbols for the default locale.
Note: This constructor may not support all
Locales. For full coverage, use the factory
methods in the DateFormat class.
Parameters:
pattern - the pattern describing the date and
time format
Throws:
NullPointerException - if the given pattern
                        is null
IllegalArgumentException - if the given pattern
                            is invalid
Could this have been avoided?
                                                                           Hindi
                                                                       (300×106, 4th)
                                                                         Shukravār




LANG=de_DE.UTF-8   java   -jar   SimpleDateFormat.jar   Freitag 11/16/2012
LANG=en_US.UTF-8   java   -jar   SimpleDateFormat.jar   Friday 11/16/2012
LANG=hi_IN.UTF-8   java   -jar   SimpleDateFormat.jar   शुकवार ११/१६/२०१२
LANG=bn_IN.UTF-8   java   -jar   SimpleDateFormat.jar   Friday 11/16/2012
LANG=bg_BG.UTF-8   java   -jar   SimpleDateFormat.jar   Петък 11/16/2012




                                                                          Bengali
                                                                       (200×106, 7th)
                                                                          শুক্রার
                                                                              ্রব
                                                                         Shukrobar
En Use
Lo glis
  ca h
    le
          India proposes:
USA retransmits:


        Use
      English
       Locale
Germany says:


   No!
USA try in German:


      Verwenden
         Sie
       Englische
        Locale
Germany says:


   Nein!
USA try manners:


      Bitte?
Germany says:


   NEIN!
USA checks what they said with
      Google translate:
mv.visitMethodInsn(INVOKESPECIAL, "java/text/SimpleDateFormat", //
                "<init>", "(Ljava/lang/String;)V");




mv.visitFieldInsn(GETSTATIC, "java/util/Locale", //
                "US", "Ljava/util/Locale;");
mv.visitMethodInsn(INVOKESPECIAL, "java/text/SimpleDateFormat", //
                "<init>", "(Ljava/lang/String;Ljava/util/Locale;)V");
Compiler API
import java.util.Random;
public class I {
    public boolean singOutOfTune() {
        return new Random().nextBoolean();
    }
}
import java.util.Random;
public class I {
    public boolean singOutOfTune() {
        return new Random().nextBoolean();
    }
}

public class You {
    public static String DID = "StandUp&WalkOutOnMe";
    public static String DIDNOT = "LendMeAnEar";
    public String wouldDo(boolean iF) {
        return iF ? "StandUp&WalkOutOnMe" : "LendMeAnEar";
    }
}
import java.util.Random;
public class I {
    public boolean singOutOfTune() {
        return new Random().nextBoolean();
    }
}

public class You {
    public static String DID = "StandUp&WalkOutOnMe";
    public static String DIDNOT = "LendMeAnEar";
    public String wouldDo(boolean iF) {
        return iF ? "StandUp&WalkOutOnMe" : "LendMeAnEar";
    }
}


public class ExistingClassesTest {
    @Test
    public void test1000times() {
        int times = 1000;
        do {
             boolean didI;
            I i = new I();
            You you = new You();
            String what = you.wouldDo(didI = i.singOutOfTune());
             assertEquals(what, didI ? You.DID : You.DIDNOT);
        } while (--times > 0);

    }
}
import java.util.Random;
public class I {
    public boolean singOutOfTune() {
        return new Random().nextBoolean();
    }
}

public class You {
    public static String DID = "StandUp&WalkOutOnMe";
    public static String DIDNOT = "LendMeAnEar";
    public String wouldDo(boolean iF) {
        return iF ? "StandUp&WalkOutOnMe" : "LendMeAnEar";
    }
}


public class ExistingClassesTest {
    @Test
    public void test1000times() {
        int times = 1000;
        do {
             boolean didI;
            I i = new I();
            You you = new You();
            String what = you.wouldDo(didI = i.singOutOfTune());
             assertEquals(what, didI ? You.DID : You.DIDNOT);
        } while (--times > 0);
        // What would you do if I sang out of tune ? The Beatles
    }
}
public class SourceStrings {
    private SourceStrings() }
    public final static String I = "                                         "
            + "import java.util.Random;                                      "
            + "public class I {                                              "
            + "    public boolean singOutOfTune() {                          "
            + "        return new Random().nextBoolean();                    "
            + "    }                                                         "
            + "}                                                             ";
    public final static String YOU = "                                       "
            + "public class You {                                            "
            + "    public static String DID = "StandUp&WalkOutOnMe";       "
            + "    public static String DIDNOT = "LendMeAnEar";            "
            + "    public String wouldDo(boolean iF) {                       "
            + "        return iF ? "StandUp&WalkOutOnMe" : "LendMeAnEar";"
            + "    }                                                         "
            + "}                                                             ";
}




class StringSourceCodeObject extends SimpleJavaFileObject {

   final String _source;

   public StringSourceCodeObject(String fqName, String source) {
       super(URI.create("string:///" + fqName.replaceAll(".", "/") //
               + Kind.SOURCE.extension), Kind.SOURCE);
       _source = source;
   }

   @Override
   public CharSequence getCharContent(boolean ignoreEncodingErrors) {
       return _source;
   }
public class CompilerAPI {
    public static void main(String args[]) throws Exception {

        /* Creating dynamic java source code file object */
        JavaFileObject iObject = new StringSourceCodeObject("I", SourceStrings.I);
        JavaFileObject youObject = new StringSourceCodeObject("You", SourceStrings.YOU);
        JavaFileObject jfObjects[] = new JavaFileObject[]{iObject, youObject};
        /* Units to compile */
        Iterable<JavaFileObject> units = Arrays.asList(jfObjects);
        /* Instantiating the java compiler */
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        /* Get compiler file manager to show what to read. */
        // (DEFAULT LISTENER, Locale.getDefault(), Charset.defaultCharset() )
        JavaFileManager manager = compiler.getStandardFileManager(null, null, null);
        /* Compilation options - here: place in target directory */
        String[] compileOptions = new String[]{"-d", "target/classes"};
        Iterable<String> options = Arrays.asList(compileOptions);
        /* Diagnostic placeholder */
        DiagnosticCollector<JavaFileObject> sink = new DiagnosticCollector<JavaFileObject>();
        /* 1st null: where to write (default), 2nd null: no annotations processed */
        CompilationTask task = compiler.getTask(null, manager, sink, options, null, units);
        /* Go, go, go */
        boolean status = task.call();
        if (!status) {
            for (Diagnostic<? extends JavaFileObject> d : sink.getDiagnostics()) {
                System.err.format("Error on line %d in %s", d.getLineNumber(), d);
            }
        }
        manager.close();// TRY to close the file manager
    }
}
public class CompilerAPITest {
    Object i;
    Object you;
    @BeforeClass
    public static void setUpClass() throws Exception {
         CompilerAPI.main(null);
    }
    @Before
    public void setUp() throws ReflectiveOperationException {
         i = Class.forName("I").newInstance();
         you = Class.forName("You").newInstance();
    }
    @After
    public void tearDown() {
         i = null;
         you = null;
    }
   @Test
    public void testMain() throws Exception {
         assertEquals(i.getClass().getName(), "I");
         assertEquals(you.getClass().getName(), "You");
         Method m1 = i.getClass().getMethod("singOutOfTune", (Class<?>[]) null);
         Object didI = m1.invoke(i, (Object[]) null);

        for (Method m2 : you.getClass().getMethods()) {
            if ("wouldDo".equals(m2.getName())) {
                Class<?>[] args = m2.getParameterTypes();
                if (1 == args.length && args[0].isAssignableFrom(boolean.class)) {
                    System.out.println("m2.invoke(you, didI)");
                    return;
                }
            }
        }
        fail("We did not find our method!");
    }
Some Links
●   Code:
    https://github.com/alshopov/WriteBytecode
●   Presentation:
    The presentation is to be improved.

I Know Kung Fu - Juggling Java Bytecode

  • 1.
    I Know KungFu – Writing Java Bytecode Alexander Shopov <ash@kambanaria.org>
  • 2.
    [ash@edge ~]$ whoami By day: Software Engineer at Cisco By night: OSS contributor Coordinator of Bulgarian Gnome TP Contacts: E-mail: ash@kambanaria.org Jabber: al_shopov@jabber.minus273.org LinkedIn: http://www.linkedin.com/in/alshopov Web: Just search “al_shopov”
  • 3.
    Please Learn AndShare License: CC-BY v3.0Creative Common
  • 4.
    Disclaimer My opinions, knowledgeand experience! Not my employer's.
  • 5.
    Plan ● What is bytecode and how to juggle it ● Why do it, why not do it ● How to do it – Class file structure – Manipulation with ASM ● How to consume your own dog food – Classloaders – Proxies – Agents
  • 6.
    What it is ● Bytecodes are the assembler of JVM. By writing new bytecode sequences or changing existing ones we can program the JVM directly – outside of the domain of the Java Programming Language; ● Lower level than writing Java - JVM assembler; ● Much easier than writing a whole (optimizing) compiler; ● Harder than using a compiler; ● Similar to the Jedi Dark Side – pathway to many abilities some consider to be unnatural.
  • 7.
    Why do it? ● Makes impossible things possible & ● Makes hard things attainable & ● Makes long tasks shorter & ● Makes slow things faster & ● Makes repetition go away & ● Makes your head blow away!!!
  • 8.
    Common use cases ● Program analysis – find bugs, unused code paths, reverse engineering, metrics, etc. ● Program generation – compilers, stub/skeleton compilers, fast layers without reflection. ● Transformation – optimize, deoptimize/obfuscate, aspect weaving, performance monitoring.
  • 9.
    Why not doit? ● Source stops being close to executable & ● Executable stops being close to any source & ● There can be better or easier ways of doing things & ● Fewer people knowing what happens means no one can help you & ● Problems will be your fault! Unit test ∀ ! & ● You take the red pill and find out the rabbit hole goes deep, deep, deep… EOT
  • 10.
    Common misuse cases ● I write it, you maintain it. ● Do it because it is cool or others are doing it. ● Over engineering / over generalization. ● Solution in a search of a problem. ● Preferring NIH to a popular solution.
  • 11.
    Bytecode crash coursein 1-2-3 F4 Thread D Thread C Thread B F3 F3 Thread A F2 F2 F2 F1 F1 F1 F1 F0 F0 F0 F0
  • 12.
    Bytecode crash coursein 1-2-3 JVM (heap) 0 1 2 3 4 5 6 … Class PC Local variables Method code F0 Class Pool of constants Stack
  • 13.
    Bytecode crash coursein 1-2-3 ● Bytecodes retrieve things from class bytestream, constant pool, local variables array ● Perform computations only on stack ● Store temporary results in local variables array ● Enter frame by method call ● Return from frame with a possible result (top thing on stack), throw top of stack ● JVM can also throw and unroll frames
  • 14.
    If you needmore information ● Check my other presentation: Lifting the Veil – Reading Java Bytecode
  • 15.
    How to doit ● Many ways with different pluses and minuses ● Easiest way to write byte code – use the Java compiler. – javac – you all know that – java compiler api – since 1.6 ● But more of that – wait for the end ● Until then…
  • 16.
    Soot jDec CGlib JCF Editor cojen Jiapi Retroweaver JavaAssist BCEL Tea Trove jReloader jclasslib Roo Serp AspectJ gnu.bytecode
  • 17.
    Soot jDec CGlib JCF Editor cojen Jiapi One of the older frameworks, Retroweaver JavaAssist BCEL Tea Trove jReloader jclasslib Roo Serp AspectJ gnu.bytecode
  • 18.
    Bytecode manipulation ● Not constrained by Java ● Not constrained by compiler ● Not constrained by source availability ● Still constrained by JVM ● Generate or change bytecode to the limits of JVM
  • 19.
    Classfile Structure, 0xcafebabe Modifiers,name, super class, interfaces Constant pool: numeric, string and type constants Source file name (optional) Enclosing class reference Annotation* Attribute* Inner class* Name Field* Modifiers, name, type Annotation* Attribute* Method* Modifiers, name, return and parameter types Annotation* Attribute* Compiled code
  • 20.
    What is ASM ● Extremely well designed bytecode manipulation library ● Modular, small and fast – pick all ● Events based – visitor (+SAX), additionally has tree API (DOM like) ● Provides a small bytecode generation variant ● Minimal – just transformation, can stack transformations, no classloading
  • 21.
    Timeline ● 1.5 – 30 Aug 2004 – up to JDK 1.6 incl. ● 2.0 – 17 May 2005 ● 3.0 – 01 Nov 2006 ● 4.0 – 29 Oct 2011 – framework for compatibility
  • 22.
    Who uses ASM? ● Languages and AOP tools: AspectJ, BeanShell, CGLIB, Clojure, Groovy, Jruby, Jython, Coroutines ● Tools and frameworks: Fractal, Terracotta, Javeleon, JRebel ● Persistence: OpenEJB, Oracle BerkleyDB, EclipseLink ● Monitoring: WebLogic, JiP ● Testing and code analysis: Cobertura, Eclipse
  • 23.
    Most JDKs UseIt! ● Oracle – Sun – HotSpot – JDK, OpenJDK – BEA/Appeal VM – JRockit ● IBM – J9 ● Azul – Zing – $JAVA_HOME/jre/lib/rt.jar – com.sun.xml.internal.ws.org.objectweb.asm – com.sun.xml.internal.ws.model.WrapperBeanGenerator ⇒ RuntimeModeler ⇒ Web Services implementation
  • 24.
    Read and explore Class Class Reader Visitor
  • 25.
    Read, transform, write Class Class Class Class Reader Visitor Visitor Writer
  • 26.
    I know whatI am doing Class Class Class Class Class Class Class Class Reader Visitor Visitor Visitor Visitor Visitor Visitor Writer
  • 27.
    I believe Ican fly, I believe I can touch the sky… Class Class Class Class Class Class Reader Visitor Visitor Visitor Visitor Writer Class Class Class Class Class Visitor Visitor Visitor Visitor Writer Class Class Class Class Class Class Reader Visitor Visitor Visitor Visitor Writer Class Class Class Class Visitor Visitor Visitor Writer Class Class Class Class Class Reader Visitor Visitor Visitor Visitor
  • 28.
    Basic Pattern OfTransformation ● Read bytestream of a class ● Generate events from that ● Make changes to the events ● Receive the events and serialize to bytestream ● Lather, rinse, repeat
  • 29.
    Basic diagram ofclass visiting User ClassVisitor visit visitSource ● Order is important visitOuterClass visitAnnotation ● Bold return other * visitAttribute visitors visitInnerClass visitFiled * ● * marks repeat vistiMethod visitEnd
  • 30.
    Basic diagram ofannotation visiting User AnnotationVisitor visit * visitEnum ● Order is important visitAnnotation visitArray ● Bold return other visitEnd visitors ● * marks repeat
  • 31.
    Basic diagram offield visiting User FieldVisitor visitAnnotation * visitAttribute ● Order is important visitEnd ● Bold return other visitors ● * marks repeat
  • 32.
    Basic diagram ofmethod visiting User MethodVisitor visitAnnotationDefault visitAnnotation * ● Order is important visitParameterAnnotation visitAttribute ● Bold return other visitCode visitors visitFrame visitXInsn * ● * marks repeat visitLabel visitTryCatchBlock visitLocalVariable visitLineNumber visitMaxs visitEnd
  • 33.
    Our humble beginning packageorg.kambanaria.writebytecode.asm; public class Zombunny { //Зомбайо public Integer getVersion() { return Integer.valueOf(1); } }
  • 34.
    Our building blocks publicclass StupidClassLoader extends ClassLoader { private Map<String, byte[]> bytes = new HashMap<String, byte[]>(); public StupidClassLoader() { super(StupidClassLoader.class.getClassLoader()); } public void provide(String className, byte[] classBytes) { bytes.put(className, classBytes); } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { byte[] classBytes = bytes.get(name); Class<?> loaded; if (classBytes != null) { loaded = defineClass(name, classBytes, 0, classBytes.length); } else { ClassLoader parent = getParent(); if (null == parent) { parent = ClassLoader.getSystemClassLoader(); } loaded = parent.loadClass(name); } return loaded; } }
  • 35.
    Our building blocks publicfinal class Utilities { private Utilities() { } public static final String CLASS_NAME = "org.kambanaria.writebytecode.asm.Zombunny"; public static final String METHOD_NAME = "getVersion"; public static String toClassPathResourceName(String clazz) { return clazz.replace('.', '/') + ".class"; } public static byte[] retrieveBytesFromClassPath(String className) throws IOException { String path = toClassPathResourceName(className); InputStream is = Utilities.class.getClassLoader().getResourceAsStream(path); InputStream classBytes = new BufferedInputStream(is); return is2bytes(classBytes); } public static byte[] patch(byte[] bytes, DemoClassAdapter adapter) { ClassReader cr = new ClassReader(bytes); cr.accept(adapter, ClassReader.SKIP_FRAMES); return adapter.getCw().toByteArray(); } public static byte[] is2bytes(InputStream is) throws IOException { int max = 1024 * 1024; byte[] bytes = new byte[max]; // 1MB int read = is.read(bytes); // Don't do that byte[] result = new byte[read]; System.arraycopy(bytes, 0, result, 0, result.length); return result; } public static Object call0ArgsMethodOn(Object o, String methodName) // throws ReflectiveOperationException { Class<?> c = o.getClass(); Method m = c.getDeclaredMethod(methodName, (Class<?>[]) null); m.setAccessible(true); return m.invoke(o, (Object[]) null); } }
  • 36.
    Our building blocks publicclass DemoClassAdapter extends ClassVisitor { public DemoClassAdapter(ClassVisitor cv) { super(Opcodes.ASM4, cv); } public ClassWriter getCw() { return (ClassWriter) cv; } }
  • 37.
    Rename class public classRename extends DemoClassAdapter { private String suffix; public Rename(ClassVisitor cv, String suffix) { super(cv); this.suffix = suffix; } @Override public void visit(int version, int access, String name, // String signature, String superName, String[] interfaces) { cv.visit(version, access, name + suffix, signature, superName, interfaces); } }
  • 38.
    Result is asif package org.kambanaria.writebytecode.asm; public class ZombunnySUFFIX { //Зомбайо public Integer getVersion() { return Integer.valueOf(1); } }
  • 39.
    Add field public classAddField extends DemoClassAdapter { public AddField(ClassVisitor cv) { super(cv); } @Override public void visitEnd() { cv.visitField(ACC_PRIVATE, "_version", // Type.getDescriptor(Integer.class), null, null); cv.visitEnd(); } }
  • 40.
    Add field public classZombunny { //Зомбайо private Integer _version; public Integer getVersion() { return Integer.valueOf(1); } }
  • 41.
    Change method public classChangeMethod extends DemoClassAdapter { public ChangeMethod(ClassVisitor cv) { super(cv); } @Override public MethodVisitor visitMethod(int access, String name, String desc, // String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); if (Utilities.METHOD_NAME.equals(name) && "()Ljava/lang/Integer;".equals(desc)) { return new DemoMethodVisitor(Opcodes.ASM4, mv); } else { return mv; } } class DemoMethodVisitor extends MethodVisitor { public DemoMethodVisitor(int version, MethodVisitor mv) { super(version, mv); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, "org/kambanaria/writebytecode/asm/Zombunny", // "_version", "Ljava/lang/Integer;"); mv.visitInsn(ARETURN); mv.visitEnd(); } } }
  • 42.
    And change method publicclass Zombunny { //Зомбайо private Integer _version; public Integer getVersion() { return _version; } }
  • 43.
    Fix constructors public classManipulateConstructors extends DemoClassAdapter { public ManipulateConstructors(ClassVisitor cv) { super(cv); } @Override public MethodVisitor visitMethod(int access, String name, String desc, // String signature, String[] exceptions) { if ("<init>".equals(name)) { MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V"); mv.visitVarInsn(ALOAD, 0); mv.visitTypeInsn(NEW, "java/lang/Integer"); mv.visitInsn(DUP); mv.visitInsn(ICONST_2); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Integer", "<init>", "(I)V"); mv.visitFieldInsn(PUTFIELD, "org/kambanaria/writebytecode/asm/Zombunny", // "_version", "Ljava/lang/Integer;"); mv.visitInsn(RETURN); mv.visitMaxs(4, 1); mv.visitEnd(); return mv; } else { return cv.visitMethod(access, name, desc, signature, exceptions); } } @Override public void visitEnd() { MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "<init>", "(Ljava/lang/Integer;)V", // null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V"); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitFieldInsn(PUTFIELD, "org/kambanaria/writebytecode/asm/Zombunny", // "_version", "Ljava/lang/Integer;"); mv.visitInsn(RETURN); mv.visitMaxs(2, 2); mv.visitEnd(); cv.visitEnd(); } }
  • 44.
    And fix constructors publicclass Zombunny { //Зомбайо private Integer _version; public Zombunny() { _version = new Integer(2); } public Zombunny(Integer version) { _version = version; } public Integer getVersion() { return _version; } }
  • 45.
    Add interface public classAddInterface extends DemoClassAdapter { private static final String COMPARABLE = "java/lang/Comparable"; public AddInterface(ClassVisitor cv) { super(cv); } @Override public void visit(int version, int access, String name, String signature,// String superName, String[] interfaces) { if (Arrays.asList(interfaces).contains(COMPARABLE)) { super.visit(version, access, name, signature, superName, interfaces); } else { int l = interfaces.length; String[] newInterfaces = new String[l + 1]; System.arraycopy(interfaces, 0, newInterfaces, 0, l); newInterfaces[l] = COMPARABLE; super.visit(version, access, name, signature, superName, newInterfaces); } } }
  • 46.
    And add interface publicclass Zombunny implements Comparable { //Зомбайо private Integer _version; public Zombunny() { _version = new Integer(2); } public Zombunny(Integer version) { _version = version; } public Integer getVersion() { return _version; } }
  • 47.
    Add interface implementation publicclass AddMethod extends DemoClassAdapter { public AddMethod(ClassVisitor cv) {super(cv);} @Override public void visitEnd() { MethodVisitor compareToObject = cv.visitMethod(ACC_PUBLIC, "compareTo", // "(Ljava/lang/Object;)I", null, null); compareToObject.visitCode(); compareToObject.visitVarInsn(ALOAD, 1); compareToObject.visitTypeInsn(INSTANCEOF, "org/kambanaria/writebytecode/asm/Zombunny"); Label lbl0 = new Label(); compareToObject.visitJumpInsn(IFNE, lbl0); compareToObject.visitTypeInsn(NEW, "java/lang/ClassCastException"); compareToObject.visitInsn(DUP); compareToObject.visitMethodInsn(INVOKESPECIAL, "java/lang/ClassCastException", // "<init>", "()V"); compareToObject.visitInsn(ATHROW); compareToObject.visitLabel(lbl0); compareToObject.visitVarInsn(ALOAD, 0); compareToObject.visitVarInsn(ALOAD, 1); compareToObject.visitTypeInsn(CHECKCAST, "org/kambanaria/writebytecode/asm/Zombunny"); compareToObject.visitMethodInsn(INVOKEVIRTUAL, "org/kambanaria/writebytecode/asm/Zombunny", // "compareTo", "(Lorg/kambanaria/writebytecode/asm/Zombunny;)I"); compareToObject.visitInsn(IRETURN); compareToObject.visitMaxs(2, 2); compareToObject.visitEnd(); MethodVisitor compareToZombunny = cv.visitMethod(ACC_PUBLIC, "compareTo", // "(Lorg/kambanaria/writebytecode/asm/Zombunny;)I", null, null); compareToZombunny.visitCode(); compareToZombunny.visitVarInsn(ALOAD, 0); compareToZombunny.visitFieldInsn(GETFIELD, "org/kambanaria/writebytecode/asm/Zombunny", // "_version", "Ljava/lang/Integer;"); compareToZombunny.visitVarInsn(ALOAD, 1); compareToZombunny.visitFieldInsn(GETFIELD, "org/kambanaria/writebytecode/asm/Zombunny", // "_version", "Ljava/lang/Integer;"); compareToZombunny.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "compareTo", // "(Ljava/lang/Integer;)I"); compareToZombunny.visitInsn(IRETURN); compareToZombunny.visitMaxs(2, 2); compareToZombunny.visitEnd(); cv.visitEnd(); } }
  • 48.
    And add interface publicclass Zombunny implements Comparable { //Зомбайо private Integer _version; public Zombunny() { _version = new Integer(2); } public Zombunny(Integer version) { _version = version; } public Integer getVersion() { return _version; } public int compareTo(Object o) { if (o instanceof Zombunny) { return compareTo((Zombunny) o); } else { throw new ClassCastException(); } } public int compareTo(Zombunny o) { return _version.compareTo(o._version); } }
  • 49.
    The class weget method from package org.kambanaria.writebytecode.asm; public class Version { private Integer _version; public Version(Integer version) { _version = version; } public Integer getVersion() { return _version; } @Override public String toString() { return "Version: " + _version; } }
  • 50.
    Chimerization public class Chimerizeextends DemoClassAdapter { protected ClassNode twig; protected MethodNode nm; private static final String TWIG_NAME = "org.kambanaria.writebytecode.asm.Version"; public Chimerize(ClassVisitor cv) throws IOException { super(cv); ClassReader rdr = new ClassReader(TWIG_NAME); twig = new ClassNode(); rdr.accept(twig, 0); for (Object o : twig.methods) { MethodNode method = (MethodNode) o; if (method.name.equals("toString")) { // Guess what is missing? nm = method; } } } @Override public void visitEnd() { MethodVisitor chimeric =cv.visitMethod(nm.access, nm.name, nm.desc, nm.signature, null); nm.instructions.resetLabels(); Remapper remapper = new Remapper() { @Override public String map(String name) { return name.replace("Version", "Zombunny"); } }; nm.accept(new RemappingMethodAdapter(nm.access, nm.desc, chimeric, remapper)); cv.visitEnd(); } }
  • 51.
    Finally public class Zombunnyimplements Comparable { //Зомбайо private Integer _version; public Zombunny() { _version = new Integer(2); } public Zombunny(Integer version) { _version = version; } public Integer getVersion() { return _version; } public int compareTo(Object o) { if (o instanceof Zombunny) { return compareTo((Zombunny) o); } else { throw new ClassCastException(); } } public int compareTo(Zombunny o) { return _version.compareTo(o._version); } @Override public String toString(){ return "Version: " + _version; } }
  • 52.
    Peek at testconstruction chain public class ChimerizeTest { Comparable sut; @Before public void setUp() throws IOException, ReflectiveOperationException { ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES + // ClassWriter.COMPUTE_MAXS); ClassVisitor cv = new AddField(new ManipulateConstructors( // new AddMethod(new AddInterface(new Chimerize(cw))))); ClassReader rdr = new ClassReader(Utilities.CLASS_NAME); rdr.accept(cv, 0); byte[] newClassBytes = cw.toByteArray(); StupidClassLoader ldr = new StupidClassLoader(); ldr.provide(Utilities.CLASS_NAME, newClassBytes); Class<?> newClass = ldr.loadClass(Utilities.CLASS_NAME); Constructor<?> constructor = newClass.getDeclaredConstructor(Integer.class); sut = (Comparable) constructor.newInstance(new Integer(42)); } }
  • 53.
    Utilities ● Type ● Generate the visitors with: Asmfier – java -classpath asm.jar:asm-util.jar:OUR_CP org.objectweb.asm.util.ASMifier CLASS ● TraceClassVisitor ● CheckClassAdapter
  • 54.
    Additional APIs ● org.objectweb.asm.commons – commonly needed adaptors ● org.objectweb.asm.xml – bridge to SAX 2.0, manipulate via XSLT, XQuery ● org.objectweb.asm.tree – deserialize to tree
  • 55.
    Classloaders ● Dynamically load software components for Java platform ● Lazy – loaded on demand, as late as possible ● Type-safe linkage – must not violate type safety, no runtime checks ● User-defined extensibility – normal, user controlled objects ● Multiple communicating namespaces – types determined by class name and classloader
  • 56.
    Classloader Chain Bootstrap CL Extension CL System CL Application Programmer primordial, native jre/lib/ext/*.jar $CLASSPATH classloader classloader jre/lib/*.jar -Djava.ext.dirs -Djava.class.path
  • 57.
    Classloader Hierarchy Application Programmer classloader classloader Bootstrap CL Extension CL System CL Application Programmer primordial, native jre/lib/ext/*.jar $CLASSPATH classloader classloader jre/lib/*.jar -Djava.ext.dirs -Djava.class.path Application Programmer classloader classloader
  • 58.
    Enterprise Classloader Hierarchy Programmer Application classloader classloader Programmer classloader Programmer Bootstrap CL Extension CL System CL classloader Application primordial, native jre/lib/ext/*.jar $CLASSPATH classloader Programmer jre/lib/*.jar -Djava.ext.dirs -Djava.class.path classloader Programmer Application classloader classloader Programmer classloader
  • 59.
    Class Loader API publicabstract class ClassLoader { protected ClassLoader(ClassLoader parent); protected ClassLoader(); protected Class<?> loadClass(String name, boolean resolve); protected Class<?> findClass(String name); protected final Class<?> defineClass(String name, byte[] b, int off, int len); protected final void resolveClass(Class<?> c); public URL getResource(String name); public Enumeration<URL> getResources(String name); public final MyClassLoader getParent(); public void setDefaultAssertionStatus(boolean); public void setPackageAssertionStatus(String packageName,boolean enabled); public void setClassAssertionStatus(String className, boolean enabled); public void clearAssertionStatus(); }
  • 60.
    When are classloaded? ● Statically: – Instance creation: new Integer(42); – Reference to static field or method: System.out; ● Dynamically: – Class.forName("java.lang.HashMap"); – Class.forName("java.lang.HashMap", boolean initialize, ClassLoader loader);
  • 61.
  • 62.
    Plugin or WebClassloaders
  • 63.
  • 64.
    Type Compatibility ● A classloader can see and use (with exact type) instances of classes loaded by the ancestral chain and the classloader itself ● Instances of classes loaded by sibling or descendant classloaders are invisible, they are just java.lang.Object Object a; "SomeClass".equals(a.getClass().getName()); a instanceof Object; (SomeClass)a -> ClassCastException ● Use reflection
  • 65.
    Proxies ● Dynamic proxy acts as a pass through/router to the real object – runtime implementations of interfaces – public, final and not abstract – extend java.lang.reflect.Proxy ● Proxy’s behaviour is determined by an implementation of java.lang.reflect.InvocationHandler
  • 66.
    Square peg ina non square hole Some object Some interface
  • 67.
    Fit it withan InvocationHandler Proxy Object public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
  • 68.
    public class MyInvocationHandlerimplements InvocationHandler { private Object delegate; public MyInvocationHandler(Object... params) { delegate = makeDelegate(); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); Class<?>[] types = method.getParameterTypes(); Method m = reachMethod(methodName, types); Object result; try { result = m.invoke(delegate, args); } catch (InvocationTargetException e) { throw e.getCause(); } return result; } private static Method reachMethod(String name, Class<?>... parTypes) { Method m = null; /* Logic to determine method */ return m; } private static Object makeDelegate(Object... params) { /* Produce real object instead */ return new Object(); } }
  • 69.
    Java agents ● Package java.lang.instrument - allow Java programming language agents to instrument programs running on the JVM. ● java ... -javaagent:jarpath[=options] ● Manifest attributes ● byte[] -> byte[]
  • 70.
  • 71.
  • 72.
    A planet, anicy atmosphere
  • 73.
  • 74.
  • 75.
    They deliver cheaperthan the world, faster than the speed of thought
  • 76.
  • 77.
    Makes them sellthe software to this country
  • 78.
    Where it failsspectacularly while clearly working in the other two ones
  • 79.
  • 80.
    Finds the culprit SimpleDateFormatfrmt = new SimpleDateFormat("E MM/dd/yyyy"); SimpleDateFormat publicSimpleDateFormat(String pattern) Constructs a SimpleDateFormat using the given pattern and the default date format symbols for the default locale. Note: This constructor may not support all Locales. For full coverage, use the factory methods in the DateFormat class. Parameters: pattern - the pattern describing the date and time format Throws: NullPointerException - if the given pattern is null IllegalArgumentException - if the given pattern is invalid
  • 81.
    Could this havebeen avoided? Hindi (300×106, 4th) Shukravār LANG=de_DE.UTF-8 java -jar SimpleDateFormat.jar Freitag 11/16/2012 LANG=en_US.UTF-8 java -jar SimpleDateFormat.jar Friday 11/16/2012 LANG=hi_IN.UTF-8 java -jar SimpleDateFormat.jar शुकवार ११/१६/२०१२ LANG=bn_IN.UTF-8 java -jar SimpleDateFormat.jar Friday 11/16/2012 LANG=bg_BG.UTF-8 java -jar SimpleDateFormat.jar Петък 11/16/2012 Bengali (200×106, 7th) শুক্রার ্রব Shukrobar
  • 82.
    En Use Lo glis ca h le India proposes:
  • 83.
    USA retransmits: Use English Locale
  • 84.
  • 85.
    USA try inGerman: Verwenden Sie Englische Locale
  • 86.
  • 87.
  • 88.
  • 89.
    USA checks whatthey said with Google translate:
  • 90.
    mv.visitMethodInsn(INVOKESPECIAL, "java/text/SimpleDateFormat", // "<init>", "(Ljava/lang/String;)V"); mv.visitFieldInsn(GETSTATIC, "java/util/Locale", // "US", "Ljava/util/Locale;"); mv.visitMethodInsn(INVOKESPECIAL, "java/text/SimpleDateFormat", // "<init>", "(Ljava/lang/String;Ljava/util/Locale;)V");
  • 91.
  • 92.
    import java.util.Random; public classI { public boolean singOutOfTune() { return new Random().nextBoolean(); } }
  • 93.
    import java.util.Random; public classI { public boolean singOutOfTune() { return new Random().nextBoolean(); } } public class You { public static String DID = "StandUp&WalkOutOnMe"; public static String DIDNOT = "LendMeAnEar"; public String wouldDo(boolean iF) { return iF ? "StandUp&WalkOutOnMe" : "LendMeAnEar"; } }
  • 94.
    import java.util.Random; public classI { public boolean singOutOfTune() { return new Random().nextBoolean(); } } public class You { public static String DID = "StandUp&WalkOutOnMe"; public static String DIDNOT = "LendMeAnEar"; public String wouldDo(boolean iF) { return iF ? "StandUp&WalkOutOnMe" : "LendMeAnEar"; } } public class ExistingClassesTest { @Test public void test1000times() { int times = 1000; do { boolean didI; I i = new I(); You you = new You(); String what = you.wouldDo(didI = i.singOutOfTune()); assertEquals(what, didI ? You.DID : You.DIDNOT); } while (--times > 0); } }
  • 95.
    import java.util.Random; public classI { public boolean singOutOfTune() { return new Random().nextBoolean(); } } public class You { public static String DID = "StandUp&WalkOutOnMe"; public static String DIDNOT = "LendMeAnEar"; public String wouldDo(boolean iF) { return iF ? "StandUp&WalkOutOnMe" : "LendMeAnEar"; } } public class ExistingClassesTest { @Test public void test1000times() { int times = 1000; do { boolean didI; I i = new I(); You you = new You(); String what = you.wouldDo(didI = i.singOutOfTune()); assertEquals(what, didI ? You.DID : You.DIDNOT); } while (--times > 0); // What would you do if I sang out of tune ? The Beatles } }
  • 96.
    public class SourceStrings{ private SourceStrings() } public final static String I = " " + "import java.util.Random; " + "public class I { " + " public boolean singOutOfTune() { " + " return new Random().nextBoolean(); " + " } " + "} "; public final static String YOU = " " + "public class You { " + " public static String DID = "StandUp&WalkOutOnMe"; " + " public static String DIDNOT = "LendMeAnEar"; " + " public String wouldDo(boolean iF) { " + " return iF ? "StandUp&WalkOutOnMe" : "LendMeAnEar";" + " } " + "} "; } class StringSourceCodeObject extends SimpleJavaFileObject { final String _source; public StringSourceCodeObject(String fqName, String source) { super(URI.create("string:///" + fqName.replaceAll(".", "/") // + Kind.SOURCE.extension), Kind.SOURCE); _source = source; } @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) { return _source; }
  • 97.
    public class CompilerAPI{ public static void main(String args[]) throws Exception { /* Creating dynamic java source code file object */ JavaFileObject iObject = new StringSourceCodeObject("I", SourceStrings.I); JavaFileObject youObject = new StringSourceCodeObject("You", SourceStrings.YOU); JavaFileObject jfObjects[] = new JavaFileObject[]{iObject, youObject}; /* Units to compile */ Iterable<JavaFileObject> units = Arrays.asList(jfObjects); /* Instantiating the java compiler */ JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); /* Get compiler file manager to show what to read. */ // (DEFAULT LISTENER, Locale.getDefault(), Charset.defaultCharset() ) JavaFileManager manager = compiler.getStandardFileManager(null, null, null); /* Compilation options - here: place in target directory */ String[] compileOptions = new String[]{"-d", "target/classes"}; Iterable<String> options = Arrays.asList(compileOptions); /* Diagnostic placeholder */ DiagnosticCollector<JavaFileObject> sink = new DiagnosticCollector<JavaFileObject>(); /* 1st null: where to write (default), 2nd null: no annotations processed */ CompilationTask task = compiler.getTask(null, manager, sink, options, null, units); /* Go, go, go */ boolean status = task.call(); if (!status) { for (Diagnostic<? extends JavaFileObject> d : sink.getDiagnostics()) { System.err.format("Error on line %d in %s", d.getLineNumber(), d); } } manager.close();// TRY to close the file manager } }
  • 98.
    public class CompilerAPITest{ Object i; Object you; @BeforeClass public static void setUpClass() throws Exception { CompilerAPI.main(null); } @Before public void setUp() throws ReflectiveOperationException { i = Class.forName("I").newInstance(); you = Class.forName("You").newInstance(); } @After public void tearDown() { i = null; you = null; } @Test public void testMain() throws Exception { assertEquals(i.getClass().getName(), "I"); assertEquals(you.getClass().getName(), "You"); Method m1 = i.getClass().getMethod("singOutOfTune", (Class<?>[]) null); Object didI = m1.invoke(i, (Object[]) null); for (Method m2 : you.getClass().getMethods()) { if ("wouldDo".equals(m2.getName())) { Class<?>[] args = m2.getParameterTypes(); if (1 == args.length && args[0].isAssignableFrom(boolean.class)) { System.out.println("m2.invoke(you, didI)"); return; } } } fail("We did not find our method!"); }
  • 99.
    Some Links ● Code: https://github.com/alshopov/WriteBytecode ● Presentation: The presentation is to be improved.