jdk/test/java/lang/instrument/asmlib/Instrumentor.java
author shurailine
Tue, 27 Oct 2015 20:06:02 -0700
changeset 33402 1156d495a525
parent 26205 c073791a67de
permissions -rw-r--r--
8140336: Add @modules for exported dependencies to jdk_core tests Reviewed-by: alanb, mchung

/*
 * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package asmlib;

import java.io.PrintStream;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassVisitor;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;

import java.util.function.Consumer;
import jdk.internal.org.objectweb.asm.Type;

public class Instrumentor {
    public static class InstrHelper {
        private final MethodVisitor mv;
        private final String name;

        InstrHelper(MethodVisitor mv, String name) {
            this.mv = mv;
            this.name = name;
        }

        public String getName() {
            return this.name;
        }

        public void invokeStatic(String owner, String name, String desc, boolean itf) {
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, desc, itf);
        }

        public void invokeSpecial(String owner, String name, String desc) {
            mv.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc, false);
        }

        public void invokeVirtual(String owner, String name, String desc) {
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, desc, false);
        }

        public void push(int val) {
            if (val >= -1 && val <= 5) {
                mv.visitInsn(Opcodes.ICONST_0 + val);
            } else if (val >= Byte.MIN_VALUE && val <= Byte.MAX_VALUE) {
                mv.visitIntInsn(Opcodes.BIPUSH, val);
            } else if (val >= Short.MIN_VALUE && val <= Short.MAX_VALUE) {
                mv.visitIntInsn(Opcodes.SIPUSH, val);
            } else {
                mv.visitLdcInsn(val);
            }
        }

        public void push(Object val) {
            mv.visitLdcInsn(val);
        }

        public void println(String s) {
            mv.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(System.class), "out", Type.getDescriptor(PrintStream.class));
            mv.visitLdcInsn(s);
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(PrintStream.class), "println", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class)), false);
        }
    }

    public static Instrumentor instrFor(byte[] classData) {
        return new Instrumentor(classData);
    }


    private final ClassReader cr;
    private final ClassWriter output;
    private ClassVisitor instrumentingVisitor = null;
    private final AtomicInteger matches = new AtomicInteger(0);

    private Instrumentor(byte[] classData) {
        cr = new ClassReader(classData);
        output = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        instrumentingVisitor = output;
    }

    public synchronized Instrumentor addMethodEntryInjection(String methodName, Consumer<InstrHelper> injector) {
        instrumentingVisitor = new ClassVisitor(Opcodes.ASM5, instrumentingVisitor) {
            @Override
            public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
                MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);

                if (name.equals(methodName)) {
                    matches.getAndIncrement();

                    mv = new MethodVisitor(Opcodes.ASM5, mv) {
                        @Override
                        public void visitCode() {
                            injector.accept(new InstrHelper(mv, name));
                        }
                    };
                }
                return mv;
            }
        };
        return this;
    }

    public synchronized Instrumentor addNativeMethodTrackingInjection(String prefix, Consumer<InstrHelper> injector) {
        instrumentingVisitor = new ClassVisitor(Opcodes.ASM5, instrumentingVisitor) {
            private final Set<Consumer<ClassVisitor>> wmGenerators = new HashSet<>();
            private String className;

            @Override
            public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
                this.className = name;
                super.visit(version, access, name, signature, superName, interfaces);
            }


            @Override
            public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
                if ((access & Opcodes.ACC_NATIVE) != 0) {
                    matches.getAndIncrement();

                    String newName = prefix + name;
                    wmGenerators.add((v)->{
                        MethodVisitor mv = v.visitMethod(access & ~Opcodes.ACC_NATIVE, name, desc, signature, exceptions);
                        mv.visitCode();
                        injector.accept(new InstrHelper(mv, name));
                        Type[] argTypes = Type.getArgumentTypes(desc);
                        Type retType = Type.getReturnType(desc);

                        boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
                        if (!isStatic) {
                            mv.visitIntInsn(Opcodes.ALOAD, 0); // load "this"
                        }

                        // load the method parameters
                        if (argTypes.length > 0) {
                            int ptr = isStatic ? 0 : 1;
                            for(Type argType : argTypes) {
                                mv.visitIntInsn(argType.getOpcode(Opcodes.ILOAD), ptr);
                                ptr += argType.getSize();
                            }
                        }

                        mv.visitMethodInsn(isStatic ? Opcodes.INVOKESTATIC : Opcodes.INVOKESPECIAL, className, newName, desc, false);
                        mv.visitInsn(retType.getOpcode(Opcodes.IRETURN));

                        mv.visitMaxs(1, 1); // dummy call; let ClassWriter to deal with this
                        mv.visitEnd();
                    });
                    return super.visitMethod(access, newName, desc, signature, exceptions);
                }
                return super.visitMethod(access, name, desc, signature, exceptions);
            }

            @Override
            public void visitEnd() {
                wmGenerators.stream().forEach((e) -> {
                    e.accept(cv);
                });
                super.visitEnd();
            }
        };

        return this;
    }

    public synchronized byte[] apply() {
        cr.accept(instrumentingVisitor, ClassReader.SKIP_DEBUG + ClassReader.EXPAND_FRAMES);

        return matches.get() == 0 ? null : output.toByteArray();
    }
}