/******************************************************************************* * Copyright (c) 2010 Eric Bodden. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Eric Bodden - initial API and implementation ******************************************************************************/ package de.bodden.tamiflex.reporting; import static org.objectweb.asm.Opcodes.ALOAD; import static org.objectweb.asm.Opcodes.ICONST_0; import static org.objectweb.asm.Opcodes.ICONST_1; import static org.objectweb.asm.Opcodes.INVOKESTATIC; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.security.ProtectionDomain; import org.objectweb.asm.ClassAdapter; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import de.bodden.tamiflex.normalizer.NameExtractor; public class ReflectionMonitor implements ClassFileTransformer { public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { if(className==null) { className = NameExtractor.extractName(classfileBuffer); } final String theClassName = className; if(!className.equals("java/lang/Class") && !className.equals("java/lang/reflect/Method") && !className.equals("java/lang/reflect/Constructor")) return null; try { // scan class binary format to find fields for toString() method ClassReader creader = new ClassReader(classfileBuffer); ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassVisitor visitor = new ClassAdapter(writer) { public MethodVisitor visitMethod(int access, String methodName, String desc, String signature, String[] exceptions) { //delegate MethodVisitor mv = cv.visitMethod(access, methodName, desc, signature, exceptions); if(theClassName.equals("java/lang/Class") && methodName.equals("forName")) { if(signature.contains("ClassLoader")) mv = new ClassForNameWithClassloaderAdapter(mv); else mv = new ClassForNameAdapter(mv); } else if(theClassName.equals("java/lang/Class") && methodName.equals("newInstance0")) { mv = new ClassNewInstanceAdapter(mv); } else if(theClassName.equals("java/lang/reflect/Method") && methodName.equals("invoke")) { mv = new MethodInvokeAdapter(mv); } else if(theClassName.equals("java/lang/reflect/Constructor") && methodName.equals("newInstance")) { mv = new ConstructorNewInstanceAdapter(mv); } return mv; }; }; creader.accept(visitor, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); return writer.toByteArray(); } catch (IllegalStateException e) { throw new IllegalClassFormatException("Error: " + e.getMessage() + " on class " + className); } catch(RuntimeException e) { e.printStackTrace(); throw e; } } static class ClassForNameAdapter extends ReportingMethodAdapter { public ClassForNameAdapter(MethodVisitor mv) { super(mv); } protected void insertCall(boolean beginningOfMethod) { //at the method entry, we pass "true" as first argument, later-on "false" mv.visitInsn(beginningOfMethod ? ICONST_1 : ICONST_0); //load first argument on stack, i.e. the name of the class to be loaded mv.visitVarInsn(ALOAD, 0); //call logging method with that Class object as argument mv.visitMethodInsn(INVOKESTATIC, "de/bodden/tamiflex/reporting/rt/ReflLogger", "classForName", "(ZLjava/lang/String;)V"); } } static class ClassForNameWithClassloaderAdapter extends ReportingMethodAdapter { public ClassForNameWithClassloaderAdapter(MethodVisitor mv) { super(mv); } protected void insertCall(boolean beginningOfMethod) { //at the method entry, we pass "true" as first argument, later-on "false" mv.visitInsn(beginningOfMethod ? ICONST_1 : ICONST_0); //load first argument on stack, i.e. the name of the class to be loaded mv.visitVarInsn(ALOAD, 0); //load second argument on stack, i.e. the boolean indicating whether to initialize the class mv.visitVarInsn(ALOAD, 1); //load third argument on stack, i.e. the ClassLoader mv.visitVarInsn(ALOAD, 2); //call logging method with that Class object as argument mv.visitMethodInsn(INVOKESTATIC, "de/bodden/tamiflex/reporting/rt/ReflLogger", "classForName", "(ZLjava/lang/String;ZLjava/lang/ClassLoader;)V"); } } static class ClassNewInstanceAdapter extends ReportingMethodAdapter { public ClassNewInstanceAdapter(MethodVisitor mv) { super(mv); } @Override protected void insertCall(boolean beginningOfMethod) { //at the method entry, we pass "true" as first argument, later-on "false" mv.visitInsn(beginningOfMethod ? ICONST_1 : ICONST_0); //load "this" on stack, i.e. the Class object mv.visitVarInsn(ALOAD, 0); //call logging method with that Class object as argument mv.visitMethodInsn(INVOKESTATIC, "de/bodden/tamiflex/reporting/rt/ReflLogger", "classNewInstance", "(ZLjava/lang/Class;)V"); } } static class MethodInvokeAdapter extends ReportingMethodAdapter { public MethodInvokeAdapter(MethodVisitor mv) { super(mv); } @Override protected void insertCall(boolean beginningOfMethod) { //at the method entry, we pass "true" as first argument, later-on "false" mv.visitInsn(beginningOfMethod ? ICONST_1 : ICONST_0); //load first parameter on the stack, i.e. the designated receiver object mv.visitVarInsn(ALOAD, 1); //load "this" on stack, i.e. the Method object mv.visitVarInsn(ALOAD, 0); //call logging method with that Method object as argument mv.visitMethodInsn(INVOKESTATIC, "de/bodden/tamiflex/reporting/rt/ReflLogger", "methodInvoke", "(ZLjava/lang/Object;Ljava/lang/reflect/Method;)V"); } } static class ConstructorNewInstanceAdapter extends ReportingMethodAdapter { public ConstructorNewInstanceAdapter(MethodVisitor mv) { super(mv); } @Override protected void insertCall(boolean beginningOfMethod) { //at the method entry, we pass "true" as first argument, later-on "false" mv.visitInsn(beginningOfMethod ? ICONST_1 : ICONST_0); //load "this" on stack, i.e. the Constructor object mv.visitVarInsn(ALOAD, 0); //call logging method with that Method object as argument mv.visitMethodInsn(INVOKESTATIC, "de/bodden/tamiflex/reporting/rt/ReflLogger", "constructorNewInstance", "(ZLjava/lang/reflect/Constructor;)V"); } } }