Necesito obtener una lista de todos los métodos de llamada para un método de interés para mí en Java. ¿Hay alguna herramienta que pueda ayudarme con esto?
Editar: Olvidé mencionar que necesito hacer esto desde un progtwig. Estoy usando Java Pathfinder y quiero ejecutarlo con todos los métodos que llaman mi método de interés.
Para analizar bytecode, recomendaría ASM . Dada una lista de Clases para analizar, se puede crear un visitante que encuentre las llamadas a los métodos que le interesan. A continuación se incluye una implementación que analiza las clases en un archivo jar.
Tenga en cuenta que ASM usa internalNames con ‘/’ en lugar de ‘.’ como un separador Especifique el método de destino como una statement estándar sin modificadores.
Por ejemplo, para enumerar los métodos que podrían estar llamando a System.out.println (“foo”) en el jar de tiempo de ejecución de java:
java -cp "classes;asm-3.1.jar;asm-commons-3.1.jar" App \ c:/java/jdk/jre/lib/rt.jar \ java/io/PrintStream "void println(String)"
Editar : fuente y números de línea agregados: tenga en cuenta que esto solo indica la última invocación al método de destino por método de llamada: la q original solo quería saber qué métodos. Lo dejo como un ejercicio para que el lector muestre los números de línea de la statement del método de llamada, o los números de línea de cada invocación de destino, dependiendo de lo que realmente está buscando. 🙂
resultados en:
LogSupport.java:44 com/sun/activation/registries/LogSupport log (Ljava/lang/String;)V LogSupport.java:50 com/sun/activation/registries/LogSupport log (Ljava/lang/String;Ljava/lang/Throwable;)V ... Throwable.java:498 java/lang/Throwable printStackTraceAsCause (Ljava/io/PrintStream;[Ljava/lang/StackTraceElement;)V -- 885 methods invoke java/io/PrintStream println (Ljava/lang/String;)V
fuente:
public class App { private String targetClass; private Method targetMethod; private AppClassVisitor cv; private ArrayList callees = new ArrayList (); private static class Callee { String className; String methodName; String methodDesc; String source; int line; public Callee(String cName, String mName, String mDesc, String src, int ln) { className = cName; methodName = mName; methodDesc = mDesc; source = src; line = ln; } } private class AppMethodVisitor extends MethodAdapter { boolean callsTarget; int line; public AppMethodVisitor() { super(new EmptyVisitor()); } public void visitMethodInsn(int opcode, String owner, String name, String desc) { if (owner.equals(targetClass) && name.equals(targetMethod.getName()) && desc.equals(targetMethod.getDescriptor())) { callsTarget = true; } } public void visitCode() { callsTarget = false; } public void visitLineNumber(int line, Label start) { this.line = line; } public void visitEnd() { if (callsTarget) callees.add(new Callee(cv.className, cv.methodName, cv.methodDesc, cv.source, line)); } } private class AppClassVisitor extends ClassAdapter { private AppMethodVisitor mv = new AppMethodVisitor(); public String source; public String className; public String methodName; public String methodDesc; public AppClassVisitor() { super(new EmptyVisitor()); } public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { className = name; } public void visitSource(String source, String debug) { this.source = source; } public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { methodName = name; methodDesc = desc; return mv; } } public void findCallingMethodsInJar(String jarPath, String targetClass, String targetMethodDeclaration) throws Exception { this.targetClass = targetClass; this.targetMethod = Method.getMethod(targetMethodDeclaration); this.cv = new AppClassVisitor(); JarFile jarFile = new JarFile(jarPath); Enumeration entries = jarFile.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (entry.getName().endsWith(".class")) { InputStream stream = new BufferedInputStream(jarFile.getInputStream(entry), 1024); ClassReader reader = new ClassReader(stream); reader.accept(cv, 0); stream.close(); } } } public static void main( String[] args ) { try { App app = new App(); app.findCallingMethodsInJar(args[0], args[1], args[2]); for (Callee c : app.callees) { System.out.println(c.source+":"+c.line+" "+c.className+" "+c.methodName+" "+c.methodDesc); } System.out.println("--\n"+app.callees.size()+" methods invoke "+ app.targetClass+" "+ app.targetMethod.getName()+" "+app.targetMethod.getDescriptor()); } catch(Exception x) { x.printStackTrace(); } } }
Editar: la pregunta original fue editada para indicar que se necesitaba una solución en tiempo de ejecución; esta respuesta se dio antes de esa edición y solo indica cómo hacerlo durante el desarrollo.
Si está utilizando Eclipse, puede hacer clic con el botón derecho en el método y seleccionar “Abrir jerarquía de llamadas” para obtener esta información.
Actualizado después de leer los comentarios: otros IDEs también lo soportan de manera similar (al menos Netbeans e IntelliJ lo hacen)
Anota el método con @Deprecated (o etiquétalo con @deprecated), activa las advertencias de degradación, ejecuta tu comstackción y observa qué advertencias se activan.
La ejecución de su bit de comstackción se puede realizar invocando un proceso externo y utilizando la API del comstackdor Java 6 .
Aparece un panel que muestra todas las referencias a estas funciones. Eclipse FTW!
No hay una forma de hacer esto (mediante progtwigción) a través de las bibliotecas de reflexión de Java; no puede solicitar un método java.lang.reflect.Method “¿a qué métodos llama?”
Eso deja otras dos opciones que puedo pensar:
Análisis estático del código fuente. Estoy seguro de que esto es lo que hace el conjunto de herramientas de Eclipse Java: puede ver el origen de Eclipse detrás del JDT y encontrar qué hace cuando le pide a Eclipse que “Encuentre referencias” para un método.
Análisis de bytecode Puede inspeccionar el bytecode para las llamadas al método. No estoy seguro de qué bibliotecas o ejemplos hay disponibles para ayudar con esto, pero no puedo imaginar que algo no exista.
En eclipse, resalta el nombre del método y luego Ctrl + Shift + G
Sí, la mayoría de los IDE modernos te permitirán buscar usos de un método o variable. Alternativamente, puede usar un depurador y establecer un punto de rastreo en la entrada del método, imprimiendo un seguimiento de stack o lo que sea cada vez que se invoca el método. Finalmente, puede usar una utilidad simple de shell para grep para el método, como
find . -name '*.java' -exec grep -H methodName {} ;
Sin embargo, el único método que le permitirá encontrar invocaciones hechas a través de algún método de reflexión sería usar el depurador.
1) En eclipse está -> haga clic derecho en el método y seleccione abrir jerarquía de llamadas o CLT+ALT+H
2) En jdeveloper está -> haga clic derecho en el método y seleccione llamadas o ALT+SHIFT+H
Lo más cerca que pude encontrar fue el método descrito en esta respuesta seleccionada StackOverflow preguntas. mira esto
Hice un pequeño ejemplo usando el de @ Chadwick. Es una prueba que evalúa si las llamadas a getDatabaseEngine () se realizan mediante métodos que implementan @Transaction.
/** * Ensures that methods that call {@link DatabaseProvider#getDatabaseEngine()} * implement the {@link @Transaction} annotation. * * @throws Exception If something occurs while testing. */ @Test public void ensure() throws Exception { final Method method = Method.getMethod( DatabaseEngine.class.getCanonicalName() + " getDatabaseEngine()"); final ArrayList faultyMethods = Lists.newArrayList(); for (Path p : getAllClasses()) { try (InputStream stream = new BufferedInputStream(Files.newInputStream(p))) { ClassReader reader = new ClassReader(stream); reader.accept(new ClassAdapter(new EmptyVisitor()) { @Override public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { return new MethodAdapter(new EmptyVisitor()) { @Override public void visitMethodInsn(int opcode, String owner, String nameCode, String descCode) { try { final Class> klass = Class.forName(Type.getObjectType(owner).getClassName()); if (DatabaseProvider.class.isAssignableFrom(klass) && nameCode.equals(method.getName()) && descCode.equals(method.getDescriptor())) { final java.lang.reflect.Method method = klass.getDeclaredMethod(name, getParameters(desc).toArray(new Class[]{})); for (Annotation annotation : method.getDeclaredAnnotations()) { if (annotation.annotationType().equals(Transaction.class)) { return; } } faultyMethods.add(method); } } catch (Exception e) { Throwables.propagate(e); } } }; } }, 0); } } if (!faultyMethods.isEmpty()) { fail("\n\nThe following methods must implement @Transaction because they're calling getDatabaseEngine().\n\n" + Joiner.on("\n").join (faultyMethods) + "\n\n"); } } /** * Gets all the classes from target. * * @return The list of classes. * @throws IOException If something occurs while collecting those classes. */ private List getAllClasses() throws IOException { final ImmutableList.Builder builder = new ImmutableList.Builder<>(); Files.walkFileTree(Paths.get("target", "classes"), new SimpleFileVisitor () { @Override public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { if (file.getFileName().toString().endsWith(".class")) { builder.add(file); } return FileVisitResult.CONTINUE; } }); return builder.build(); } /** * Gets the list of parameters given the description. * * @param desc The method description. * @return The list of parameters. * @throws Exception If something occurs getting the parameters. */ private List> getParameters(String desc) throws Exception { ImmutableList.Builder> obj = new ImmutableList.Builder<>(); for (Type type : Type.getArgumentTypes(desc)) { obj.add(ClassUtils.getClass(type.getClassName())); } return obj.build(); }
Puede hacer esto con algo en su IDE como “Buscar usos” (que es como se llama en Netbeans y JDeveloper). Un par de cosas a anotar: