I searched on Google how to generate subclass by asm, there seem to be few people who are concerned about this issue. Is this demand itself not suitable? Perhaps the most common thing that asm does is to add extra code before and after the method by AdviceAdapter.
I think that generating a subclass is also a very common requirement.In fact, it is not easy to do this. How to make all public or protected methods automatically override the parent class's methods, just like HttpServletRequest' subclass HttpServletRequestWrapper did.
I use org.ow2.asm:asm:6.2 to implement it as follow:
public class InheritMethodVisitor extends ClassVisitor {
/** the return opcode for different type */
public static final Map<Type, Integer> RETURN_OPCODES = new HashMap<>();
/** the load opcode for different type */
public static final Map<Type, Integer> LOAD_OPCODES = new HashMap<>();
static {
RETURN_OPCODES.put(Type.VOID_TYPE, Opcodes.RETURN);
RETURN_OPCODES.put(Type.BOOLEAN_TYPE, Opcodes.IRETURN);
RETURN_OPCODES.put(Type.BYTE_TYPE, Opcodes.IRETURN);
RETURN_OPCODES.put(Type.SHORT_TYPE, Opcodes.IRETURN);
RETURN_OPCODES.put(Type.INT_TYPE, Opcodes.IRETURN);
RETURN_OPCODES.put(Type.LONG_TYPE, Opcodes.LRETURN);
RETURN_OPCODES.put(Type.FLOAT_TYPE, Opcodes.FRETURN);
RETURN_OPCODES.put(Type.DOUBLE_TYPE, Opcodes.DRETURN);
LOAD_OPCODES.put(Type.BOOLEAN_TYPE, Opcodes.ILOAD);
LOAD_OPCODES.put(Type.BYTE_TYPE, Opcodes.ILOAD);
LOAD_OPCODES.put(Type.SHORT_TYPE, Opcodes.ILOAD);
LOAD_OPCODES.put(Type.INT_TYPE, Opcodes.ILOAD);
LOAD_OPCODES.put(Type.LONG_TYPE, Opcodes.LLOAD);
LOAD_OPCODES.put(Type.FLOAT_TYPE, Opcodes.FLOAD);
LOAD_OPCODES.put(Type.DOUBLE_TYPE, Opcodes.DLOAD);
}
private Class<?> superClass;
private ClassVisitor cv;
public InheritMethodVisitor(int api, ClassVisitor classVisitor, Class<?> superClass) {
super(api);
this.cv = classVisitor;
this.superClass = Objects.requireNonNull(superClass);
}
#Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
//Inherit all public or protect methods
if (Modifier.isStatic(access) || Modifier.isPrivate(access)) return null;
if (name.equals("<init>") || Modifier.isProtected(access)) access = Opcodes.ACC_PUBLIC;
MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions);
Type methodType = Type.getMethodType(descriptor);
Type[] argumentTypes = methodType.getArgumentTypes();
if (!name.equals("<init>")) {
//TODO Customize what you want to do
//System.out.println(name)
mv.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(System.class), "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn(name);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(PrintStream.class), "println", "(Ljava/lang/String;)V", false);
}
//load this
mv.visitVarInsn(Opcodes.ALOAD, 0);
//load arguments
IntStream.range(0, argumentTypes.length).forEach(value ->
mv.visitVarInsn(LOAD_OPCODES.getOrDefault(argumentTypes[value], Opcodes.ALOAD), value + 1)
);
//invoke super.{method}()
mv.visitMethodInsn(
Opcodes.INVOKESPECIAL,
Type.getInternalName(superClass),
name,
descriptor,
false);
//handle return
mv.visitInsn(RETURN_OPCODES.getOrDefault(methodType.getReturnType(), Opcodes.ALOAD));
//for ClassWriter.COMPUTE_FRAMES the max*s not required correct
int maxLocals = argumentTypes.length + 1;
mv.visitMaxs(maxLocals + 2, maxLocals);
mv.visitEnd();
return null;
}}
#Test
public void create() throws Exception {
//generate a subclass of AdviceAdapter to add logger info
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
//generate class name
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "com/github/Generated",
null, Type.getInternalName(AdviceAdapter.class), null);
//generate overwrite methods
new ClassReader(AdviceAdapter.class.getName())
.accept(
new InheritMethodVisitor(Opcodes.ASM6, cw, AdviceAdapter.class),
ClassReader.EXPAND_FRAMES
);
//TODO AdviceAdapter.class.getSuperclass() not supported
//end
cw.visitEnd();
//save to file
byte[] bytes = cw.toByteArray();
Files.write(Paths.get(getClass().getResource("/com/github").getPath(), "Generated.class"), bytes);
//show use of it
ClassReader classReader = new ClassReader(AdviceAdapter.class.getName());
classReader.accept(
new ClassVisitor(Opcodes.ASM6, new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES)) {
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
try {
Class<?> aClass = Class.forName("com.github.Generated");
return (MethodVisitor) aClass.getConstructors()[0].newInstance(Opcodes.ASM6, methodVisitor, access, name, descriptor);
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException(e);
}
}
},
ClassReader.EXPAND_FRAMES
);
}
It works, but it's not very easy. In fact, I think TraceClassVisitor works easy.
You should really see ByteBuddy library - as it is much more suitable for your goal, there is no reason to use such low level library for such generic task.
In ASM you need to do this all alone - you can't just tell it to generate and implement methods for you, ASM library is all about just modifying raw bytecodes, so you need to read all methods of super class i generate bytecode for each of them. You can use asm module to print asm code for you: https://static.javadoc.io/org.ow2.asm/asm/5.2/org/objectweb/asm/util/ASMifier.html .
or from command line:
java -classpath asm.jar:asm-util.jar \
org.objectweb.asm.util.ASMifier \
java.lang.Runnable
And this will create working ASM code to generate given class, like this:
package asm.java.lang;
import org.objectweb.asm.*;
public class RunnableDump implements Opcodes {
public static byte[] dump() throws Exception {
ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
AnnotationVisitor av0;
cw.visit(V1_5, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
"java/lang/Runnable", null, "java/lang/Object", null);
{
mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "run", "()V",
null, null);
mv.visitEnd();
}
cw.visitEnd();
return cw.toByteArray();
}
}
(There is also plugin to Intellij IDEA that allows you to see bytecode and ASMifed code, just look for ASM in plugins)
And to just create a subclass all you need to do is to pass name of that subclass when generating class in ASM, like in example above:
cw.visit(V1_5, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
"java/lang/Runnable", null, "java/lang/Object", null);
java/lang/Object is superclass that you want to extend, done.
But for methods you need to manually loop over all methods and generate the code you want.
Unless you want to create something less typical or something more generic like own library for generating proxy classes like ByteBuddy - then it is better to use some already existing solutions: ByteBuddy, Javassist, CGLib. (all of them use ASM to generate bytecode too)
We use ASM to generate bytecode for the Saxon XSLT/XQuery processor, and generating a subclass of a known abstract class is the way we normally do things. I won't pretend it's easy, and I don't have time to write you a tutorial, and unfortunately I can't publish our code, but I can assure you that it's possible. I don't think you have to do anything special for overriding methods.
We have a class Generator which subcasses ASM's GeneratorAdapter.
We create the class using something like:
String className = "xxx" + compiler.getUniqueNumber();
ClassVisitor cv = new ClassWriter(flags);
cv.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, className, null,
"com/saxonica/ee/bytecode/iter/CompiledBlockIterator", new String[]{});
// CompiledBlockIterator is the superclass name
// generate constructor
Method m = Method.getMethod("void <init> ()");
Generator ga = new Generator(Opcodes.ACC_PUBLIC, m, false, cv);
ga.loadThis();
ga.invokeConstructor(Type.getType(CompiledBlockIterator.class), m);
ga.returnValue();
ga.endMethod();
and then proceed to generate other methods in the same way.
Related
I'm new to Java agent instrumentation and ASM bytecode instrumentation. I took the code from this UCLA tutorial and used it for javagent instrumentation using java.lang.instrument.
First question, is there anything in ASM bytecode library that is incompatible with javaagent instrumentation?
Here's the program in a somewhat redacted form:
public class Instrumenter {
public static void premain(String args, Instrumentation inst) throws Exception {
Transformer tr = new Transformer();
inst.addTransformer(tr);
}
}
class Transformer implements ClassFileTransformer {
public Transformer() {
}
#Override
public byte[] transform( ClassLoader loader, String className, Class<?> klass, ProtectionDomain domain, byte[] klassFileBuffer ) throws IllegalClassFormatException {
byte[] barray;
ClassWriter cwriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
ClassReader creader;
try {
creader = new ClassReader(new ByteArrayInputStream(klassFileBuffer));
} catch (Exception exc) {
throw new IllegalClassFormatException(exc.getMessage());
}
ClassVisitor cvisitor = new ClassAdapter(cwriter);
creader.accept(cvisitor, 0);
barray = cwriter.toByteArray();
return barray;
}
}
class ClassAdapter extends ClassVisitor implements Opcodes {
public ClassAdapter(ClassVisitor cv) {
super(ASM7, cv);
}
#Override
public MethodVisitor visitMethod( final int access, final String name, final String desc, final String signature, final String[] exceptions ) {
this.pwriter.println(ClassAdapter.nextMethodId + "," + this.className + "#" + name);
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
if (mv == null) {
return null;
} else {
return new MethodAdapter(mv);
}
}
}
class MethodAdapter extends MethodVisitor implements Opcodes {
public MethodAdapter(final MethodVisitor mv) {
super(ASM7, mv);
}
#Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;");
mv.visitLdcInsn("CALL " + name);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
// do call
mv.visitMethodInsn(opcode, owner, name, desc, itf);
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;");
mv.visitLdcInsn("RETURN " + name);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
}
So the javaagent instrumentation works on small programs. I tried running it on the DaCapo benchmark suite and it throws a StackOverflowError like so:
Exception: java.lang.StackOverflowError thrown from the UncaughtExceptionHandler in thread "main"
When I remove the instructions added in visitMethodInsn, the agent runs successfully. I researched this bit some more and found something in the ASM docs about having to call MethodVisitor.visitMaxs. This seems to be the most likely reason for the StackOverflowError.
So further questions:
Is this the case? Do I have to call visitMaxs at some point? If so, where?
If not, then what am I doing wrong? Or what should I be doing to ensure there's no stack overflow?
When you register a ClassFileTransformer, it will be invoked for every subsequently loaded class. This may include classes used by the print operation itself you are injecting, if these classes have not been used before. You are injecting print statements for every method invocation, including constructor invocations, and the operations behind System.err.println(…) will involve method invocations and object constructions, so if these got instrumented, they will enter another printing operation and this recursion will lead to a StackOverflowError.
Apparently, an UncaughtExceptionHandler is installed, which tries to print the StackOverflowError, which, being itself instrumented the same way, will lead again to a StackOverflowError, so the error message reads like “StackOverflowError thrown from the UncaughtExceptionHandler”.
You should restrict, which classes you are instrumenting. E.g. you may not transform the class when its loader is null, to exclude all classes loaded by the bootstrap class loader. Or you check the name argument to exclude classes starting with java.. Or more elaborated solution would be to enhance the code you’re injecting, to detect when it is within an injected print operation and not go into recursion.
By the way, use new ClassReader(klassFileBuffer) and you don’t need a try … catch block. Further, when you insert code as simple as yours, you may use ClassWriter.COMPUTE_MAXS instead of ClassWriter.COMPUTE_FRAMES, to avoid expensive recalculations of the stack map frames. Since you don’t specify SKIP_FRAMES to the reader, it will report the original frames to the writer and ASM is capable of adapting the positions, so it’s no problem when you insert some simple instructions. Only when you insert or remove branches or introduce variables which have to persist across branches, you need to adapt or recalculate the frames.
I'm trying to remove the method body of test() in the following program so that nothing is printed to the Console. I'm using using ASM 5.2 but everything I've tried doesn't seem to have any effect.
Can someone explain what I'm doing wrong and also point me to some up-to-date tutorials or documentation on ASM? Almost everything Iv'e found on Stackoverflow and the ASM website seems outdated and/or unhelpful.
public class BytecodeMods {
public static void main(String[] args) throws Exception {
disableMethod(BytecodeMods.class.getMethod("test"));
test();
}
public static void test() {
System.out.println("This is a test");
}
private static void disableMethod(Method method) {
new MethodReplacer()
.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, method.getName(), Type.getMethodDescriptor(method), null, null);
}
public static class MethodReplacer extends ClassVisitor {
public MethodReplacer() {
super(Opcodes.ASM5);
}
#Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
return null;
}
}
}
You are not supposed to invoke the methods of a visitor directly.
The correct way to use a ClassVisitor, is to create a ClassReader with the class file bytes of the class you’re interested in and pass the class visitor to the its accept method. Then, all the visit methods will be called by the class reader according to the artifacts found in the class file.
In this regard, you should not consider the documentation outdated, just because it refers to an older version number. E.g. this document describes that process correctly and it speaks for the library that no fundamental change was necessary between the versions 2 and 5.
Still, visiting a class does not change it. It helps analyzing it and perform actions when encountering a certain artifact. Note that returning null is not an actual action.
If you want to create a modified class, you need a ClassWriter to produce the class. A ClassWriter implements ClassVisitor, also class visitors can be chained, so you can easily create a custom visitor delegating to a writer, that will produce a class file identical to the original one, unless you override a method to intercept the recreation of a feature.
But note that returning null from visitMethod does more than removing the code, it will remove the method entirely. Instead, you have to return a special visitor for the specific method which will reproduce the method but ignore the old code and create a sole return instruction (you are allowed to omit the last return statement in source code, but not the return instruction in the byte code).
private static byte[] disableMethod(Method method) {
Class<?> theClass = method.getDeclaringClass();
ClassReader cr;
try { // use resource lookup to get the class bytes
cr = new ClassReader(
theClass.getResourceAsStream(theClass.getSimpleName()+".class"));
} catch(IOException ex) {
throw new IllegalStateException(ex);
}
// passing the ClassReader to the writer allows internal optimizations
ClassWriter cw = new ClassWriter(cr, 0);
cr.accept(new MethodReplacer(
cw, method.getName(), Type.getMethodDescriptor(method)), 0);
byte[] newCode = cw.toByteArray();
return newCode;
}
static class MethodReplacer extends ClassVisitor {
private final String hotMethodName, hotMethodDesc;
MethodReplacer(ClassWriter cw, String name, String methodDescriptor) {
super(Opcodes.ASM5, cw);
hotMethodName = name;
hotMethodDesc = methodDescriptor;
}
// invoked for every method
#Override
public MethodVisitor visitMethod(
int access, String name, String desc, String signature, String[] exceptions) {
if(!name.equals(hotMethodName) || !desc.equals(hotMethodDesc))
// reproduce the methods we're not interested in, unchanged
return super.visitMethod(access, name, desc, signature, exceptions);
// alter the behavior for the specific method
return new ReplaceWithEmptyBody(
super.visitMethod(access, name, desc, signature, exceptions),
(Type.getArgumentsAndReturnSizes(desc)>>2)-1);
}
}
static class ReplaceWithEmptyBody extends MethodVisitor {
private final MethodVisitor targetWriter;
private final int newMaxLocals;
ReplaceWithEmptyBody(MethodVisitor writer, int newMaxL) {
// now, we're not passing the writer to the superclass for our radical changes
super(Opcodes.ASM5);
targetWriter = writer;
newMaxLocals = newMaxL;
}
// we're only override the minimum to create a code attribute with a sole RETURN
#Override
public void visitMaxs(int maxStack, int maxLocals) {
targetWriter.visitMaxs(0, newMaxLocals);
}
#Override
public void visitCode() {
targetWriter.visitCode();
targetWriter.visitInsn(Opcodes.RETURN);// our new code
}
#Override
public void visitEnd() {
targetWriter.visitEnd();
}
// the remaining methods just reproduce meta information,
// annotations & parameter names
#Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
return targetWriter.visitAnnotation(desc, visible);
}
#Override
public void visitParameter(String name, int access) {
targetWriter.visitParameter(name, access);
}
}
The custom MethodVisitor does not get chained to the method visitor returned by the class writer. Configured this way, it will not replicate the code automatically. Instead, performing no action will be the default and only our explicit invocations on the targetWriter will produce code.
At the end of the process, you have a byte[] array containing the changed code in the class file format. So the question is, what to do with it.
The easiest, most portable thing you can do, is to create a new ClassLoader, which creates a new Class from these bytes, which has the same name (as we didn’t change the name), but is distinct from the already loaded class, because it has a different defining class loader. We can access such dynamically generated class only through Reflection:
public class BytecodeMods {
public static void main(String[] args) throws Exception {
byte[] code = disableMethod(BytecodeMods.class.getMethod("test"));
new ClassLoader() {
Class<?> get() { return defineClass(null, code, 0, code.length); }
} .get()
.getMethod("test").invoke(null);
}
public static void test() {
System.out.println("This is a test");
}
…
In order to make this example do something more notable than doing nothing, you could alter the message instead,
using the following MethodVisitor
static class ReplaceStringConstant extends MethodVisitor {
private final String matchString, replaceWith;
ReplaceStringConstant(MethodVisitor writer, String match, String replacement) {
// now passing the writer to the superclass, as most code stays unchanged
super(Opcodes.ASM5, writer);
matchString = match;
replaceWith = replacement;
}
#Override
public void visitLdcInsn(Object cst) {
super.visitLdcInsn(matchString.equals(cst)? replaceWith: cst);
}
}
by changing
return new ReplaceWithEmptyBody(
super.visitMethod(access, name, desc, signature, exceptions),
(Type.getArgumentsAndReturnSizes(desc)>>2)-1);
to
return new ReplaceStringConstant(
super.visitMethod(access, name, desc, signature, exceptions),
"This is a test", "This is a replacement");
If you want to change the code of an already loaded class or intercept it right before being loaded into the JVM, you have to use the Instrumentation API.
The byte code transformation itself doesn’t change, you’ll have to pass the source bytes into the ClassReader and get the modified bytes back from the ClassWriter. Methods like ClassFileTransformer.transform(…) will already receive the bytes representing the current form of the class (there might have been previous transformations) and return the new bytes.
The problem is, this API isn’t generally available to Java applications. It’s available for so-called Java Agents, which must have been either, started together with the JVM via startup options or get loaded dynamically in an implementation-specific way, e.g. via the Attach API.
The package documentation describes the general structure of Java Agents and the related command line options.
At the end of this answer is a program demonstrating how to use the Attach API to attach to your own JVM to load a dummy Java Agent that will give the program access to the Instrumentation API. Considering the complexity, I think, it became apparent, that the actual code transformation and turning the code into a runtime class or using it to replace a class on the fly, are two different tasks that have to collaborate, but whose code you usually want to keep separated.
The easier way is to create a MethodNode instance and replace the body with a new InsnList. First, you need the original class representation. You can get it just like #Holger suggested.
Class<?> originalClass = method.getDeclaringClass();
ClassReader classReader;
try {
cr = new ClassReader(
originalClass.getResourceAsStream(originalClass.getSimpleName()+".class"));
} catch(IOException e) {
throw new IllegalStateException(e);
}
Then create a ClassNode and replace the method body.
//Create the CLassNode
ClassNode classNode = new ClassNode();
classReader.accept(classNode,0);
//Search for the wanted method
final List<MethodNode> methods = classNode.methods;
for(MethodNode methodNode: methods){
if(methodNode.name.equals("test")){
//Replace the body with a RETURN opcode
InsnList insnList = new InsnList();
insnList.add(new InsnNode(Opcodes.RETURN));
methodNode.instructions = insnList;
}
}
Before generating the new class, you will need a ClassLoader with a public defineClass() method. Just like this.
public class GenericClassLoader extends ClassLoader {
public Class<?> defineClass(String name, byte[] b) {
return defineClass(name, b, 0, b.length);
}
}
Now you can generate the actual class.
//Generate the Class
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
classNode.accept(classWriter);
//Define the representation
GenericClassLoader classLoader = new GenericClassLoader();
Class<?> modifiedClass = classLoader.defineClass(classNode.name, classWriter.toByteArray());
We have an ability to compile java code dynamically on the fly.
I know at least Java-Runtime-Compiler and InMemoryJavaCompiler
But seems they cannot compile class which depends on some class from certain classloader.
Is it possible to dynamically compile java code which depends on classes available only in specific classloader? Let's say:
ClassLoader classloader = ... // only this CL can load class 'com.External'
String source = "public class MyClass extends com.External {}";
Class<?> compiled = DesiredDynamicCompiler.compile("MyClass", source, classloader);
// last argument is like an information to compiler where to search all dependencies
To provide more insight: I would like to do in java what exactly GroovyClassLoader can do in groovy:
GroovyClassLoader groovyClassLoader = new GroovyClassLoader(classLoader);
Class<?> parsedClass = groovyClassLoader.parseClass("some source");
That code can parse class which depends on classes available only in specified classloader.
There is no way to use a ClassLoader as reference, unless it is capable of providing the class bytes of its defined classes. I.e., if you have a Class instance representing a top-level class, you can use classInstance.getResourceAsStream(classInstance.getSimpleName()+".class") to try to get hands on the class bytes. If you have access to the bytes that make up the dynamic class, you can make them available to the java compiler via a JavaFileManager implementation.
The compiler API is part of the standard API and doesn’t require 3rd party libraries. The following code demonstrates this by compiling a test class first, then setting the necessary environment to compile a second class depending on the class just created in the previous step:
// customize these, if you want, null triggers default behavior
DiagnosticListener<JavaFileObject> diagnosticListener = null;
Locale locale = null;
// the first class, to be present at runtime only
String class1 = "package test;\npublic class Class1 {}";
JavaCompiler c = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fm
= c.getStandardFileManager(diagnosticListener, locale, Charset.defaultCharset());
// define where to store compiled class files - use a temporary directory
fm.setLocation(StandardLocation.CLASS_OUTPUT, Collections.singleton(
Files.createTempDirectory("compile-test").toFile()));
JavaCompiler.CompilationTask task = c.getTask(null, fm,
diagnosticListener, Collections.emptySet(), Collections.emptySet(),
Collections.singleton(new SimpleJavaFileObject(
URI.create("string:///Class1.java"), Kind.SOURCE) {
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return class1;
}
}));
if(task.call()) {
FileObject fo = fm.getJavaFileForInput(
StandardLocation.CLASS_OUTPUT, "test.Class1", Kind.CLASS);
// these are the class bytes of the first class
byte[] class1bytes = Files.readAllBytes(Paths.get(fo.toUri()));
// the actual task: define a class dependent on the first class
String class2 = "package test;\npublic class Class2 { Class1 variable; }";
// create a file object representing the dynamic class
JavaFileObject jo = new SimpleJavaFileObject(
URI.create("runtime:///test/Class1.class"), Kind.CLASS) {
#Override public InputStream openInputStream() throws IOException {
return new ByteArrayInputStream(class1bytes);
}
};
// and a custom file manager knowing how to locate that class
JavaFileManager myFM = new ForwardingJavaFileManager(fm) {
#Override
public JavaFileObject getJavaFileForInput(
JavaFileManager.Location location, String className, Kind kind)
throws IOException {
if(location==StandardLocation.CLASS_PATH&&className.equals("test.Class1")) {
return jo;
}
return super.getJavaFileForInput(location, className, kind);
}
#Override
public boolean hasLocation(JavaFileManager.Location location) {
return location==StandardLocation.CLASS_PATH || super.hasLocation(location);
}
#Override
public Iterable list(JavaFileManager.Location location,
String packageName, Set kinds, boolean recurse) throws IOException {
if(location==StandardLocation.CLASS_PATH
&& (packageName.equals("test") || recurse&&packageName.isEmpty())) {
return Collections.singleton(jo);
}
return super.list(location, packageName, kinds, recurse);
}
#Override
public String inferBinaryName(
JavaFileManager.Location location, JavaFileObject file) {
if(file==jo) return "test.Class1";
return super.inferBinaryName(location, file);
}
};
// compile the second class using the custom file manager to locate dependencies
task = c.getTask(null, myFM,
diagnosticListener, Collections.emptySet(), Collections.emptySet(),
Collections.singleton(new SimpleJavaFileObject(
URI.create("string:///Class2.java"), Kind.SOURCE) {
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return class2;
}
}));
if(task.call()) {
fo = fm.getJavaFileForInput(
StandardLocation.CLASS_OUTPUT, "test.Class2", Kind.CLASS);
// there we have the compiled second class
byte[] class2bytes = Files.readAllBytes(Paths.get(fo.toUri()));
}
}
Of course, this is only for demonstrating the principle. You surely want to create factory methods for the file objects and use Maps for remembering them, etc.
It’s also possible to replace the temporary directory with a custom in-memory storage. But the key point remains, that the compiler needs to be able to access the class bytes. It won’t use loaded runtime classes.
You should have all the dependencies on your class path. The tools you've referenced use Java Compiler API under the cover anyway.
It doesn't interact with classes in current JVM's memory, it only searches for dependencies in the classpath.
You can follow through CompilerUtils -> com.sun.tools.javac.api.JavacTool -> further to get some feeling of what happens there.
One thing you can try to do is to have your dynamically compiled dependencies dumped to proper place in classpath as .class files so that your compilation process will pick them up.
I am trying to pass two generics with known types (accumulationFunction, resultBindings) into the reflective invoke routine below, but I'm having trouble. Can someone help me understand how this can be achieved?
package com.amir.method.compiler;
//hidden imports
import java.util.Set;
public class CompiledReferencedAttributeMethod implements CompiledMethod {
final Class<?> generatedClazz;
//how do i pass the arguments below into the invoke routine??
final KnownWorkData<AccumulationFunction> accumulationFunction;
final KnownWorkData<Set<Executable<InstanceSetValue>>> resultBindings;
public CompiledReferencedAttributeMethod(final int hash,
final KnownWorkData<AccumulationFunction> accumulationFunction,
final KnownWorkData<Set<Executable<InstanceSetValue>>> resultBindings) {
this.generatedClazz = ReferencedAttributeMethodGenerator.get().compileMethod(
"com.amir.hotspot.GeneratedGRAMethod" +hash, "dynamicGra", accumulationFunction, resultBindings);
this.accumulationFunction = accumulationFunction;
this.resultBindings = resultBindings;
}
#Override
public WorkDataValue invokeCompiled(final Instance onInst,
final ExecutionParms parm,
final WorkDataDefinition returntype,
final TaskContext context) {
try {
return (WorkDataValue) this.generatedClazz
.getMethod("dynamicGra",
Instance.class,
ExecutionParms.class,
WorkDataDefinition.class,
TaskContext.class)
.invoke(null, onInst, parm, returntype, this.accumulationFunction, this.resultBindings, context);
} catch(Exception e) {
throw new ExecuteCompiledMethodException(this.toString(), e);
}
}
}
As you cannot overload method with generics and different parameters (see: Oracle’s tutorial “Restrictions on Generics”) you don't have to bother about generics with the reflection API.
There is no ambiguity, you can simply get your method like that:
Method m = this.generatedClazz.getMethod("dynamicGra",
Instance.class,
ExecutionParms.class,
WorkDataDefinition.class,
KnownWorkData.class,
KnownWorkData.class,
TaskContext.class);
Then you invoked with null so it means that dynamicGra must be static, if it's not the case you must pass the instance of generatedClazz on which you want to call the method :
Object instance = this.generatedClazz.newInstance(); // A new one or any other reference of generatedClazz
m.invoke(instance, onInst, parm, returntype, this.accumulationFunction, this.resultBindings, context);
In a Java Project of mine, I would like to find out programmatically which classes from a given API are used. Is there a good way to do that? Through source code parsing or bytecode parsing maybe? Because Reflection won't be of any use, I'm afraid.
To make things simpler: there are no wildcard imports (import com.mycompany.api.*;) anywhere in my project, no fully qualified field or variable definitions (private com.mycompany.api.MyThingy thingy;) nor any Class.forName(...) constructs. Given these limitations, it boils down to parsing import statements, I guess. Is there a preferred approach to do this?
You can discover the classes using ASM's Remapper class (believe it or not). This class is actually meant to replace all occurrences of a class name within bytecode. For your purposes, however, it doesn't need to replace anything.
This probably doesn't make a whole lot of sense, so here is an example...
First, you create a subclass of Remapper whose only purpose in life is to intercept all calls to the mapType(String) method, recording its argument for later use.
public class ClassNameRecordingRemapper extends Remapper {
private final Set<? super String> classNames;
public ClassNameRecordingRemapper(Set<? super String> classNames) {
this.classNames = classNames;
}
#Override
public String mapType(String type) {
classNames.add(type);
return type;
}
}
Now you can write a method like this:
public Set<String> findClassNames(byte[] bytecode) {
Set<String> classNames = new HashSet<String>();
ClassReader classReader = new ClassReader(bytecode);
ClassWriter classWriter = new ClassWriter(classReader, 0);
ClassNameRecordingRemapper remapper = new ClassNameRecordingRemapper(classNames);
classReader.accept(remapper, 0);
return classNames;
}
It's your responsibility to actually obtain all classes' bytecode.
EDIT by seanizer (OP)
I am accepting this answer, but as the above code is not quite correct, I will insert the way I used this:
public static class Collector extends Remapper{
private final Set<Class<?>> classNames;
private final String prefix;
public Collector(final Set<Class<?>> classNames, final String prefix){
this.classNames = classNames;
this.prefix = prefix;
}
/**
* {#inheritDoc}
*/
#Override
public String mapDesc(final String desc){
if(desc.startsWith("L")){
this.addType(desc.substring(1, desc.length() - 1));
}
return super.mapDesc(desc);
}
/**
* {#inheritDoc}
*/
#Override
public String[] mapTypes(final String[] types){
for(final String type : types){
this.addType(type);
}
return super.mapTypes(types);
}
private void addType(final String type){
final String className = type.replace('/', '.');
if(className.startsWith(this.prefix)){
try{
this.classNames.add(Class.forName(className));
} catch(final ClassNotFoundException e){
throw new IllegalStateException(e);
}
}
}
#Override
public String mapType(final String type){
this.addType(type);
return type;
}
}
public static Set<Class<?>> getClassesUsedBy(
final String name, // class name
final String prefix // common prefix for all classes
// that will be retrieved
) throws IOException{
final ClassReader reader = new ClassReader(name);
final Set<Class<?>> classes =
new TreeSet<Class<?>>(new Comparator<Class<?>>(){
#Override
public int compare(final Class<?> o1, final Class<?> o2){
return o1.getName().compareTo(o2.getName());
}
});
final Remapper remapper = new Collector(classes, prefix);
final ClassVisitor inner = new EmptyVisitor();
final RemappingClassAdapter visitor =
new RemappingClassAdapter(inner, remapper);
reader.accept(visitor, 0);
return classes;
}
Here's a main class to test it using:
public static void main(final String[] args) throws Exception{
final Collection<Class<?>> classes =
getClassesUsedBy(Collections.class.getName(), "java.util");
System.out.println("Used classes:");
for(final Class<?> cls : classes){
System.out.println(" - " + cls.getName());
}
}
And here's the Output:
Used classes:
- java.util.ArrayList
- java.util.Arrays
- java.util.Collection
- java.util.Collections
- java.util.Collections$1
- java.util.Collections$AsLIFOQueue
- java.util.Collections$CheckedCollection
- java.util.Collections$CheckedList
- java.util.Collections$CheckedMap
- java.util.Collections$CheckedRandomAccessList
- java.util.Collections$CheckedSet
- java.util.Collections$CheckedSortedMap
- java.util.Collections$CheckedSortedSet
- java.util.Collections$CopiesList
- java.util.Collections$EmptyList
- java.util.Collections$EmptyMap
- java.util.Collections$EmptySet
- java.util.Collections$ReverseComparator
- java.util.Collections$ReverseComparator2
- java.util.Collections$SelfComparable
- java.util.Collections$SetFromMap
- java.util.Collections$SingletonList
- java.util.Collections$SingletonMap
- java.util.Collections$SingletonSet
- java.util.Collections$SynchronizedCollection
- java.util.Collections$SynchronizedList
- java.util.Collections$SynchronizedMap
- java.util.Collections$SynchronizedRandomAccessList
- java.util.Collections$SynchronizedSet
- java.util.Collections$SynchronizedSortedMap
- java.util.Collections$SynchronizedSortedSet
- java.util.Collections$UnmodifiableCollection
- java.util.Collections$UnmodifiableList
- java.util.Collections$UnmodifiableMap
- java.util.Collections$UnmodifiableRandomAccessList
- java.util.Collections$UnmodifiableSet
- java.util.Collections$UnmodifiableSortedMap
- java.util.Collections$UnmodifiableSortedSet
- java.util.Comparator
- java.util.Deque
- java.util.Enumeration
- java.util.Iterator
- java.util.List
- java.util.ListIterator
- java.util.Map
- java.util.Queue
- java.util.Random
- java.util.RandomAccess
- java.util.Set
- java.util.SortedMap
- java.util.SortedSet
I think the following might help you out:
Class Dependency Analyzer
Dependency Finder
Compiler Tree API
byte code analysis - the fully qualified names should be in the constant pool
Something like this perhaps:
import java.io.*;
import java.util.Scanner;
import java.util.regex.Pattern;
public class FileTraverser {
public static void main(String[] args) {
visitAllDirsAndFiles(new File("source_directory"));
}
public static void visitAllDirsAndFiles(File root) {
if (root.isDirectory())
for (String child : root.list())
visitAllDirsAndFiles(new File(root, child));
process(root);
}
private static void process(File f) {
Pattern p = Pattern.compile("(?=\\p{javaWhitespace}*)import (.*);");
if (f.isFile() && f.getName().endsWith(".java")) {
try {
Scanner s = new Scanner(f);
String cls = "";
while (null != (cls = s.findWithinHorizon(p, 0)))
System.out.println(cls);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
}
You may want to take comments into account, but it shouldn't be too hard. You could also make sure you only look for imports before the class declaration.
I use DependencyFinder exactly for that purpose. It can analyse bytecode and extract all dependencies, then dump a report in txt or xml format (see the DependencyExtractor tool). You should be able to programatically analyse the report from your application's code.
I have integrated this in my build process in order to check that certain APIs are NOT used by an application.
You may want to use STAN for that.
The "Couplings View" visualizes the dependencies to your API in a nice graph.
If you use Eclipse. Try using the profiling tools. It doesn't only tells which classes are being used, but tells much more about it. The results will be something like:
There is a very good quickstart at:
http://www.eclipse.org/tptp/home/documents/tutorials/profilingtool/profilingexample_32.html
Thanks Adam Paynter, It helped me. But what I was looking for is to fetch the dependent classes (recursively)- which means take a feature from a projects. So, need to get all the classes associated with a particular classes and again the used classes of those classes and so on. and also get the jars. So, I Created my own Java Dependency Resolver project Which will find the dependent Classes/jars for a particular class in a Project. I am sharing it here that may come to any use of some body.