/* * @(#) $(JCGO)/reflgen/com/ivmaisoft/jcgorefl/TraceJni.java -- * "TraceJni" utility source (part of JCGO). ** * Project: JCGO (http://www.ivmaisoft.com/jcgo/) * Copyright (C) 2001-2009 Ivan Maidanski <ivmai@ivmaisoft.com> * Use is subject to license terms. No warranties. All rights reserved. */ /* * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. ** * This software 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 (GPL) for more details. */ /** * The utility launches "main" method of the specified class, collects * Class/JNI constructor/method/field reflection dependency info (i.e. * which reflected constructors, methods, fields are used and by which * Java and/or native methods) and stores it into the data file. ** * To collect reflection dependency info for native callers, the native * library ("trjnic") is loaded and initialized (unless "-n" command-line * option is specified). ** * To collect reflection dependency info for the callers written in Java, * the standard "java.lang.Class" class should be manually patched (the * interception hooks should be added as described bellow) and forced to * be loaded by VM (e.g., by specifying java "-Xbootclasspath/p" option). */ package com.ivmaisoft.jcgorefl; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.io.BufferedReader; import java.io.CharArrayWriter; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.PrintStream; import java.io.PrintWriter; import java.util.Hashtable; import java.util.Vector; public final class TraceJni extends Thread { private static PrintStream systemOut; private static PrintStream systemErr; private static final Hashtable insideHandler = new Hashtable(); private static final Vector curEntries = new Vector(); private static String datFileName; private static Hashtable oldSet; private static Hashtable currSet; private TraceJni() {} /** * Usage in java/lang/Class.java: * * public Object newInstance() * throws InstantiationException, IllegalAccessException * { * com.ivmaisoft.jcgorefl.TraceJni.intercept_newInstance(this); * ... * } */ public static final void intercept_newInstance(Class This) { if (This != null) processInterceptInner(true, This, "<init>", "()", null, true); } /** * Usage in java/lang/Class.java: * * public Constructor getConstructor(Class[] parameterTypes) * throws NoSuchMethodException * { * com.ivmaisoft.jcgorefl.TraceJni.intercept_getConstructor(false, this, * parameterTypes); * ... * } * * ... * * public Constructor getDeclaredConstructor(Class[] parameterTypes) * throws NoSuchMethodException * { * com.ivmaisoft.jcgorefl.TraceJni.intercept_getConstructor(true, this, * parameterTypes); * ... * } */ public static final void intercept_getConstructor(boolean isDeclared, Class This, Class[] parameterTypes) { /* ignore isDeclared */ if (This != null) processInterceptInner(true, This, "<init>", "()", parameterTypes, true); } /** * Usage in java/lang/Class.java: * * public Constructor[] getConstructors() * { * com.ivmaisoft.jcgorefl.TraceJni.intercept_getConstructors(false, this); * ... * } * * ... * * public Constructor[] getDeclaredConstructors() * { * com.ivmaisoft.jcgorefl.TraceJni.intercept_getConstructors(true, this); * ... * } */ public static final void intercept_getConstructors(boolean isDeclared, Class This) { if (This != null) processInterceptInner(isDeclared, This, "<init>", "(*)", null, true); } /** * Usage in java/lang/Class.java: * * public Method getMethod(String name, Class[] parameterTypes) * throws NoSuchMethodException * { * com.ivmaisoft.jcgorefl.TraceJni.intercept_getMethod(false, this, name, * parameterTypes); * ... * } * * ... * * public Method getDeclaredMethod(String name, Class[] parameterTypes) * throws NoSuchMethodException * { * com.ivmaisoft.jcgorefl.TraceJni.intercept_getMethod(true, this, name, * parameterTypes); * ... * } */ public static final void intercept_getMethod(boolean isDeclared, Class This, String name, Class[] parameterTypes) { if (currSet != null && This != null && name != null && !isSpecialSynthMethod(name)) processInterceptInner(isDeclared, This, name, "()", parameterTypes, true); } /** * Usage in java/lang/Class.java: * * public Method[] getMethods() * { * com.ivmaisoft.jcgorefl.TraceJni.intercept_getMethods(false, this); * ... * } * * ... * * public Method[] getDeclaredMethods() * { * com.ivmaisoft.jcgorefl.TraceJni.intercept_getMethods(true, this); * ... * } */ public static final void intercept_getMethods(boolean isDeclared, Class This) { if (This != null) processInterceptInner(isDeclared, This, "*", "(*)", null, true); } /** * Usage in java/lang/Class.java: * * public Field getField(String name) * throws NoSuchFieldException * { * com.ivmaisoft.jcgorefl.TraceJni.intercept_getField(false, this, name); * ... * } * * ... * * public Field getDeclaredField(String name) * throws NoSuchFieldException * { * com.ivmaisoft.jcgorefl.TraceJni.intercept_getField(true, this, name); * ... * } */ public static final void intercept_getField(boolean isDeclared, Class This, String name) { if (This != null && name != null) processInterceptInner(isDeclared, This, name, "", null, true); } /** * Usage in java/lang/Class.java: * * public Field[] getFields() * { * com.ivmaisoft.jcgorefl.TraceJni.intercept_getFields(false, this); * ... * } * * ... * * public Field[] getDeclaredFields() * { * com.ivmaisoft.jcgorefl.TraceJni.intercept_getFields(true, this); * ... * } */ public static final void intercept_getFields(boolean isDeclared, Class This) { if (This != null) processInterceptInner(isDeclared, This, "*", "", null, true); } public static final void main(String[] args) { if (args == null) { try { TraceJni.class.getDeclaredMethod("processIntercept", new Class[] { Class.class, String.class, String.class }); } catch (NoSuchMethodException e) {} processIntercept(null, null, null); } if (args.length < 2) { System.out.println("JCGO TraceJni utility v1.2"); System.out.println( "Copyright (C) 2001-2009 Ivan Maidanski <ivmai@ivmaisoft.com>"); System.out.println( "This is free software. All rights reserved. See README file."); System.out.println("The utility collects Class/JNI ctor/method/field" + " reflection dependency info."); System.out.println(""); System.out.println( "Arguments: [-n] file.dat [+file2.dat ...] <classname> [args ...]"); System.out.println( "Option \"-n\" disables loading of the native library (\"trjnic\")."); if (args.length > 0) System.exit(1); return; } boolean loadJniLib = true; int pos = 0; if (args[0].equals("-n") && args.length > 2) { loadJniLib = false; pos = 1; } datFileName = args[pos]; systemOut = System.out; systemErr = System.err; System.out.println("TraceJni: Reading data files..."); pos = loadOldInfo(args, pos); if (pos == 0) System.exit(2); if (loadJniLib) { try { System.loadLibrary("trjnic"); } catch (UnsatisfiedLinkError e) { System.err.println("Error: cannot load native library: " + e.getMessage()); System.exit(3); } } Class aclass = null; try { aclass = Class.forName(args[pos]); } catch (ClassNotFoundException e) { System.err.println("Error: class not found: " + args[pos]); System.exit(4); } if ((aclass.getModifiers() & Modifier.PUBLIC) == 0) { System.err.println("Error: class is not public: " + aclass.getName()); System.exit(4); } Method method = null; try { System.out.println("TraceJni: Starting application..."); method = aclass.getDeclaredMethod("main", new Class[] { String[].class }); } catch (NoSuchMethodException e) { System.err.println("Error: no main(String[]) method"); System.exit(5); } if ((method.getModifiers() & (Modifier.PUBLIC | Modifier.STATIC)) != (Modifier.PUBLIC | Modifier.STATIC)) { System.err.println("Error: method 'main' is not public static"); System.exit(5); } String[] args2 = new String[args.length - pos - 1]; System.arraycopy(args, pos + 1, args2, 0, args2.length); Runtime.getRuntime().addShutdownHook(new TraceJni()); if (loadJniLib) initIntercept(); try { currSet = new Hashtable(); method.invoke(null, new Object[] { args2 }); } catch (Exception e) { currSet = null; systemOut.println(""); systemErr.println("Error: cannot invoke main method!"); System.exit(5); } } /** * This method is called from "trjnic" native library (JNI). */ static final void processIntercept(Class clazz, String name, String sig) { if (currSet != null && clazz != null && name != null && sig != null && !name.equals("<clinit>")) processInterceptInner(name.equals("<init>"), clazz, name, sig.substring(0, sig.indexOf(')') + 1), null, false); } public final void run() { storeInfo(); } private static int loadOldInfo(String[] args, int pos) { oldSet = new Hashtable(); try { if (loadOneFile(datFileName, true)) { while (++pos < args.length - 1) { String filename = args[pos]; if (filename.length() == 0 || filename.charAt(0) != '+') break; if (filename.length() == 1) { if (args.length - 2 == pos) break; filename = args[++pos]; } else filename = filename.substring(1); if (!loadOneFile(filename, false)) { pos = 0; break; } } } else pos = 0; } catch (SecurityException e) { pos = 0; } return pos; } private static boolean loadOneFile(String filename, boolean isFirst) { BufferedReader reader; try { reader = new BufferedReader(new FileReader(filename)); } catch (IOException e) { if (isFirst) return true; System.err.println("Error: cannot open data file: " + filename); return false; } try { String str; while ((str = reader.readLine()) != null) if ((str = str.trim()).length() > 0) oldSet.put(str, ""); reader.close(); } catch (IOException e) { System.err.println("Error: cannot read data file: " + filename); return false; } return true; } static final void storeInfo() { if (currSet == null) return; currSet = null; int count = curEntries.size(); systemOut.println(""); systemOut.println(" TraceJni: Dependencies found: " + count); if (count != 0) { try { PrintWriter writer = new PrintWriter(new FileWriter(datFileName, true)); writer.println(""); for (int i = 0; i < count; i++) writer.println((String) curEntries.elementAt(i)); writer.close(); systemOut.println(" TraceJni: Data saved!"); return; } catch (IOException e) {} catch (SecurityException e) {} systemErr.println(" TraceJni: Error: cannot write data file!"); } } private static boolean isSpecialSynthMethod(String name) { return name.equals("class$") || name.startsWith("access$"); } private static boolean isSpecialFieldExcl(String name) { return name.equals("$VALUES") || name.equals("$assertionsDisabled") || name.equals("ENUM$VALUES") || name.equals("cl$") || name.startsWith("$SWITCH_TABLE$") || name.startsWith("$SwitchMap$") || name.startsWith("array$") || name.startsWith("class$"); } private static void processInterceptInner(boolean isDeclared, Class clazz, String name, String sig, Class[] types, boolean isNotJniCall) { if (currSet != null) { try { Thread currThread = Thread.currentThread(); if (insideHandler.put(currThread, "") == null) { String str = getCallerString(new Throwable(), isNotJniCall); String callerClassName = "?"; String callerMethodName = "?"; int pos = -1; if (str != null && (pos = str.lastIndexOf('.')) > 0) { callerClassName = str.substring(0, pos); callerMethodName = str.substring(pos + 1); } Hashtable oldSetL = oldSet; if (oldSetL != null && (pos <= 0 || ((isNotJniCall || !callerClassName.startsWith("java.lang.")) && oldSetL.get(callerClassName.substring(0, pos = callerClassName.lastIndexOf('.') + 1) + "*") == null && (!callerMethodName.equals("<clinit>") || !callerClassName.substring(pos).startsWith("$Proxy"))))) { if (name.equals("*") || sig.equals("(*)")) { String className = clazz.getName(); try { if (name.equals("<init>")) { Constructor[] ctors = isDeclared ? clazz.getDeclaredConstructors() : clazz.getConstructors(); int len = ctors.length; for (int i = 0; i < len; i++) addNewEntry(callerClassName, callerMethodName, className, name, toMethodSig(ctors[i].getParameterTypes())); } else { if (sig.length() > 0) { Method[] methods = isDeclared ? clazz.getDeclaredMethods() : clazz.getMethods(); int len = methods.length; for (int i = 0; i < len; i++) { Method method = methods[i]; String methodName = method.getName(); if (!isSpecialSynthMethod(methodName)) addNewEntry(callerClassName, callerMethodName, method.getDeclaringClass().getName(), methodName, toMethodSig(method.getParameterTypes())); } } else { Field[] fields = isDeclared ? clazz.getDeclaredFields() : clazz.getFields(); int len = fields.length; for (int i = 0; i < len; i++) { Field field = fields[i]; String fieldName = field.getName(); if (!isDeclared || !isSpecialFieldExcl(fieldName)) addNewEntry(callerClassName, callerMethodName, field.getDeclaringClass().getName(), fieldName, sig); } } } } catch (SecurityException e) { addNewEntry(callerClassName, callerMethodName, className, name, sig); } } else { if ((types == null || (sig = toMethodSig(types)) != null) && (isDeclared || (clazz = declaringClassOf(clazz, name, sig, types, isNotJniCall)) != null)) { String className = clazz.getName(); if (name.equals("<init>") && className.substring( className.lastIndexOf('.') + 1).startsWith("$Proxy")) className = toProxyClassName(clazz.getInterfaces()); addNewEntry(callerClassName, callerMethodName, className, name, sig); } } } insideHandler.remove(currThread); } } catch (Throwable e) { insideHandler.remove(Thread.currentThread()); if (e instanceof ThreadDeath) throw (ThreadDeath) e; if (e instanceof OutOfMemoryError) throw (OutOfMemoryError) e; try { PrintStream out = systemOut; if (out != null) out.println(""); out = systemErr; if (out != null) out.println(" TraceJni: Interception processing error: " + e.toString()); } catch (Throwable e2) {} System.exit(6); } } } private static String getCallerString(Throwable exc, boolean ignoreJavaLang) { CharArrayWriter writer = new CharArrayWriter(); PrintWriter printwriter = new PrintWriter(writer); exc.printStackTrace(printwriter); printwriter.flush(); String trace = writer.toString(); int pos = strNextLinePos(trace, 0); String str = null; if (pos > 0 && (pos = strNextLinePos(trace, pos)) > 0) do { pos = strNextLinePos(trace, pos); if (pos <= 0) break; int endPos = trace.indexOf('(', pos); int next = strNextLinePos(trace, pos); if (next <= 0) next = trace.length(); if (endPos <= pos || endPos >= next) break; int beginPos = trace.lastIndexOf(' ', endPos - 1); if (beginPos < pos) break; str = trace.substring(beginPos + 1, endPos); } while (ignoreJavaLang && str.startsWith("java.lang.")); return str; } private static int strNextLinePos(String str, int pos) { int next = str.indexOf('\n', pos); int next2 = str.indexOf('\r', pos); return (next > next2 ? next : next2) + 1; } private static Class declaringClassOf(Class clazz, String name, String sig, Class[] types, boolean isPublicOnly) { try { if (sig.length() > 0) { if (types != null || (types = decodeMethodSig(sig, clazz)) != null) { Method m = null; try { m = clazz.getMethod(name, types); } catch (NoSuchMethodException e) { if (isPublicOnly) return null; m = getMethodOf(clazz, name, types); } if (m != null) clazz = m.getDeclaringClass(); } } else { Field f = null; try { f = clazz.getField(name); } catch (NoSuchFieldException e) { if (isPublicOnly) return null; f = getFieldOf(clazz, name); } if (f != null) clazz = f.getDeclaringClass(); } } catch (SecurityException e) {} return clazz; } private static Field getFieldOf(Class clazz, String name) { Field f = null; do { try { f = clazz.getDeclaredField(name); } catch (NoSuchFieldException e) {} if (f != null) break; Class[] interfaces = clazz.getInterfaces(); for (int i = 0; i < interfaces.length; i++) if ((f = getFieldOf(interfaces[i], name)) != null) return f; clazz = clazz.getSuperclass(); } while (clazz != null); return f; } private static Method getMethodOf(Class clazz, String name, Class[] types) { Method m = null; do { try { m = clazz.getDeclaredMethod(name, types); } catch (NoSuchMethodException e) {} if (m != null) break; Class[] interfaces = clazz.getInterfaces(); for (int i = 0; i < interfaces.length; i++) if ((m = getMethodOf(interfaces[i], name, types)) != null) return m; clazz = clazz.getSuperclass(); } while (clazz != null); return m; } private static String toMethodSig(Class[] types) { StringBuffer sb = new StringBuffer(); int len = types.length; sb.append('('); for (int i = 0; i < len; i++) { Class clazz = types[i]; if (clazz != null) { Class aclass; while ((aclass = clazz.getComponentType()) != null) { sb.append('['); clazz = aclass; } } else clazz = Object.class; char ch = getPrimTypeChar(clazz); sb.append(ch); if (ch == 'L') { String className = clazz.getName(); if (className.substring( className.lastIndexOf('.') + 1).startsWith("$Proxy")) return null; sb.append(className.replace('.', '/')); sb.append(';'); } } sb.append(')'); return sb.toString(); } private static char getPrimTypeChar(Class clazz) { if (clazz == boolean.class) return 'Z'; if (clazz == byte.class) return 'B'; if (clazz == char.class) return 'C'; if (clazz == short.class) return 'S'; if (clazz == int.class) return 'I'; if (clazz == long.class) return 'J'; if (clazz == float.class) return 'F'; if (clazz == double.class) return 'D'; return 'L'; } private static Class[] decodeMethodSig(String sig, Class clazz) { int len = sig.length(); if (len <= 1 || sig.charAt(0) != '(') return null; int pos = 1; int count = 0; char ch; while ((ch = sig.charAt(pos)) != ')') { if (ch != '[') { if (ch == 'L' && (pos = sig.indexOf(';', pos + 1)) < 0) return null; count++; } if (++pos >= len) return null; } Class[] types = new Class[count]; pos = 1; for (int i = 0; i < count; i++) { int next = pos; while ((ch = sig.charAt(next)) == '[') next++; next = (ch == 'L' ? sig.indexOf(';', next + 1) : next) + 1; Class type = classForSig(sig.substring(pos, next), clazz); if (type == null || type == void.class) return null; types[i] = type; pos = next; } return types; } private static Class classForSig(String sig, Class clazz) { int nameLen = sig.length(); Class aclass = null; if (nameLen > 0) { char ch = sig.charAt(0); if (nameLen == 1) return getPrimitiveClass(ch); if (sig.indexOf('.', 0) < 0) { if (ch == 'L') { if (sig.charAt(nameLen - 1) != ';') return null; sig = sig.substring(1, nameLen - 1); } else if (ch != '[') return null; try { aclass = Class.forName(sig.replace('/', '.'), true, clazz != null ? clazz.getClassLoader() : null); } catch (ClassNotFoundException e) {} } } return aclass; } private static Class getPrimitiveClass(char ch) { switch (ch) { case 'Z': return boolean.class; case 'B': return byte.class; case 'C': return char.class; case 'S': return short.class; case 'I': return int.class; case 'J': return long.class; case 'F': return float.class; case 'D': return double.class; case 'V': return void.class; } return null; } private static String toProxyClassName(Class[] interfaces) { String className = "$Proxy"; String pkgPrefix = null; int len = interfaces.length; for (int i = 0; i < len; i++) { Class clazz = interfaces[i]; String name = clazz.getName(); if (pkgPrefix == null && (clazz.getModifiers() & Modifier.PUBLIC) == 0) pkgPrefix = name.substring(0, name.lastIndexOf('.') + 1); className = className + "$00" + toProxyNamePart(name); } return pkgPrefix != null ? pkgPrefix + className : className; } private static String toProxyNamePart(String name) { int pos = -1; while ((pos = name.indexOf('$', pos + 1) + 1) > 0) name = name.substring(0, pos) + "$" + name.substring(pos); pos = -1; while ((pos = name.indexOf('.', pos + 1) + 1) > 0) name = name.substring(0, pos - 1) + "$0" + name.substring(pos); return name; } private static void addNewEntry(String callerClassName, String callerMethodName, String className, String name, String sig) { Hashtable currSetL = currSet; Hashtable oldSetL = oldSet; if (currSetL != null && oldSetL != null && sig != null) { sig = (className.equals(callerClassName) ? "" : className + ".") + name + sig; String str = callerClassName + "." + callerMethodName + "(*):" + sig; if (currSetL.get(str) == null && oldSetL.get(str) == null && oldSetL.get(callerClassName + ".<native>(*):" + sig) == null && oldSetL.get(callerClassName + ".*(*):" + sig) == null && currSetL.put(str, "") == null) { curEntries.addElement(str); printPlusChar(); } } } private static void printPlusChar() { PrintStream out = systemOut; if (out != null) { try { out.print("+"); out.flush(); } catch (RuntimeException e) {} } } /** * This method is called to initialize "trjnic" native library (JNI). */ private static native void initIntercept(); }