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());
Related
I have some theoretical knowledge related to Design Patterns and now I have some issues to make these info and another ones in action.
I have the following 2 methods in my ServiceImpl class:
#Override
public MultipartFile exportA() throws IOException {
// repeated lines-I same as exportB method (code omitted for brevity)
// other lines special to exportA method
// repeated lines-II same as exportB method (code omitted for brevity)
}
#Override
public MultipartFile exportB() throws IOException {
// repeated lines-I same as exportA method (code omitted for brevity)
// other lines special to exportB method
// repeated lines-II same as exportA method (code omitted for brevity)
}
As it is shown, there are repeated parts in all of these methods. So, should I create 2 methods for repeated lines-I and II, eand then move these code blocks to these newly created 2 methods? Or, is there a better way for Design Patterns?
If I have well understood your statement, this sounds to me a
Builder Pattern that you are looking for. You methods are building a MultipartFile whereas the build process itself depends on 'arguments/parameters' (here I guess sheet file path) and distinct code (the one you referred to as "other lines special to this method").
For that I would create a class MultipartFileBuilder that does the staff and that I would call in each method; of course, by means of setting the appropriate parameters and "code" each time. The code is simply an implementation of the java.util.function.Consumer<T> functional interface used in the following code (*2) and other parameters are using simple setters as well (here (*1)).
Note that I invoked the Consumer<T> as lambda expression here (the c->... construct). And note also that the type parameter <T> in Consumer<T> here is a new class I introduced MultipartFileBuildContext to allow multiple information to be passed to the code you are willing to write in each method. I guest the 'sheet' var would be a starting point. You can add other information if you need to.
To summer up things, this is how the code would look :
#Override
public MultipartFile exportMethodA() throws IOException {
return new MultipartFileBuilder()
.setSheetPath("sheetA/path") (*1)
.setAction(c->{
//(*2) do your staff here for method A
// the "other lines special to this method"
XSSFSheet sheet=c.getSheet()
...
}).build();
}
#Override
public MultipartFile exportMethodB() throws IOException {
return new MultipartFileBuilder()
.setSheetPath("sheetB/path") (*1)
.setAction(c->{
//(*2) do your staff here for method B
// the "other lines special to this method"
XSSFSheet sheet=c.getSheet()
...
}).build();
}
class MultipartFileBuildContext {
private XSSFSheet sheet;
public MultipartFileBuildContext(XSSFSheet sheet){this.sheet=sheet;}
public String getSheetPath() {
return sheetPath;
}
}
class MultipartFileBuilder {
String sheetPath;
Consumer<MultipartFileBuildContext> action;
public String getSheetPath() {
return sheetPath;
}
public MultipartFileBuilder setSheetPath(String sheetPath) {
this.sheetPath = sheetPath;
return this;
}
public Consumer<MultipartFileBuildContext> getAction() {
return action;
}
public MultipartFileBuilder setAction(Consumer<MultipartFileBuildContext> action) {
this.action = action;
return this;
}
public MockMultipartFile build() {
// repeated lines
workbook = new XSSFWorkbook();
sheet = workbook.createSheet(sheetPath);
//
if(action!=null){
MultipartFileBuildContext c=new MultipartFileBuildContext(sheet);
action.accept(c);
}
// repeated lines ======================================================
outputFile = File.createTempFile(...);
try (FileOutputStream outputStream = new FileOutputStream(outputFile)) {
workbook.write(outputStream);
} catch (IOException e) {
LoggingUtils.error("Writing failed ", e);
}
final FileInputStream input = new FileInputStream(outputFile);
final String fileName = TextBundleUtil.read(...);
return new MockMultipartFile(fileName,
fileName, CONTENT_TYPE, IOUtils.toByteArray(input));
//
}
}
At the end, this pattern needs to be used with care because you need to factorize all that you can to make the builder really useful, but not too much to make it a "boat of anything". For instance, you can have as input the sheet path or an inputstream of it to make it more useful/generic.
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 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.
We have a java process that calls some method of class X. Class X has timeout static field which decides how long thread should wait for in case of some error. Now, I want to change that value without changing my java process (I don't want deployment, and this change is experimental). How can I use java agent to change this timeout value to say 1 minute (1*60*1000)
Class X {
....
// timeout = 5 minutes
private static long timeout = 5*60*1000;
....
}
In short, how to write java agent to change value of a static variable. I have went through some tutorials, but none of those explain how to do this. I do not have access to main method. The project is run by an IOC container.
Thanks,
Rishi
Using reflection, you can implement this quite easily:
class EasyFieldAlterationAgent {
public static void premain(String args) throws Exception {
Field field = X.class.getDeclaredField("timeout");
field.setAccessible(true);
field.setValue(null, 42L); // set your value here.
}
}
Note that this will change the field not before but right after class loading time. If this is not possible for you to work with, you might also just redefine the class itself what I would only recommend if:
Your security setting does not allow you to use reflection.
You need to change the value befor the class's type initializer is executed.
If you want to really change the field before loading the class, you are lucky enough that you want to change the value of a field that is both static and that defines a primitive value. Such fields store their values directly at the site of the field. Using an agent, you can define a ClassFileTransformer that simply alters the value of the field on the fly. ASM is a good tool for implementing such a simple transformation. Using this tool, you could implement your agent approximately as follows:
class FieldAlterationAgent {
public static void premain(String args, Instrumentation inst) {
instrumentation.addTransformer(new ClassFileTransformer() {
#Override
public void byte[] transform(ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer)
throws IllegalClassFormatException {
if (!className.equals("X") {
return classFileBuffer;
}
ClassWriter classWriter = new ClassWriter(new ClassVisitor() {
#Override
public FieldVisitor visitField(int access,
String name,
String desc,
String signature,
Object value) {
if(name.equals("timeout") {
value = 42L; // set value here, make sure its a long!
}
return super.visitField(access, name, desc, signature, value);
}
}, 0);
new ClassReader(classFileBuffer).accept(classWriter);
return classWriter.toByteArray();
}
});
}
}
You can tell that this latter version requires a lot more code and requires your agent to be packed together with its ASM dependency.
To apply the agent, put either class in a jar file and put it onto the agent path.
I wish to "extend" enum XResult in enum XStatus, by copying the values of XResult into XStatus.
Here is how I'm doing it. If item is not found in XResult, description is left null:
private final String description;
private final Whatever funStuff;
private XStatus(){
String d=null;
Whatever f=null;
try{
XResult xResult = XResult.valueOf(this.name());
d = XResult.toString();
f = XResult.getWhatever();
}
catch (Exception e){
}
this.description = d;
this.funStuff = f;
}
The issue is that if XResult does not contain such an item, it would throw IllegalArgumentException.
Question:
Is this the best way to copy values of one enum into another? The reason for the question is, I am deeply troubled with having the expense of try-catch to define the enum.
Is the try-catch worthwhile for its expense, if indeed this is the only way.
What are my alternatives, besides unreliable manual copying (which does not track changes in XResult)?
Rest of the code:
For the curious, this is the rest of the code that is inconsequential to the question:
private XStatus(final String d){
this.description = d;
}
public String toString(){
if (description==null || description.length()==0)
return doSomethingTo( this.name() );
return description;
}
public getWhatever(){ /*similar to toString */ }
Well, enums are in principle just Java objects so you could use reflection to test if XResult contains the desired value or even add new values at runtime to your enum class.
Just for reference, and in case someone suggests this as answer, my initial way was to do it this way:
private final String description;
private final Whatever funStuff;
private XStatus(XResult xResult){
this.description = xResult.toString();
this.funStuff = xResult.getWhatever();
}
Which means, instead of declaring
enum XStatus {
//from XResult
NOT_FOUND,
DELETED,
PROCESSED,
TBD,
blah, blah ...
}
I would have to declare
enum XStatus {
//from XResult
NOT_FOUND(XResult.NOT_FOUND),
DELETED(XResult.DELETED),
PROCESSED(XResult.PROCESSED),
TBD(XResult.TBD),
blah, blah ...
}
A further technique which might solve your problem to dynamically change the XResult class on loading it to your application is to simply manipulate its content at load-time using a byte manipulation framework like f.e. Javassist.
This requires however that your application hasn't loaded the enum before and you run your own classloader which will load the enum (and all the classes that access the enum) in that classloader (or in a child classloader of the classloader that loaded the bytes of the enum you have modified).
On using javassist you could do something like that (haven't done it actually with enums yet)
public byte[] modifyClass(String className)
{
try
{
// load the bytes from the .class file that actually contains the original definition of the XResult enum
byte[] xResultBytes = ...
// define a classpool javassist will use to find the definition for classes
ClassPool cp = ClassPool.getDefault();
// add a new search path for class definitions to the class path
cp = cp.insertClassPath(new ClassClassPath(this.getClass()));
// add the jar file containing java classes needed to the classpath
cp = cp.insertClassPath(jarFile.getAbsolutePath());
// as you do not have loaded XResult with any classloader yet you do not have a Class file of it
// so you need to provide the loaded bytes and the fully-qualified class name of the enum
cp = cp.appendClassPath(new ByteArrayClassPath(className, xResultBytes));
// now you are good to go with modifying the class
// first create a javassist classrepresentation of the bytes loaded
CtClass cc = cp.get(className);
// you can't modify frozen classes
if (!cc.isFrozen())
{
// you only want to instrument the XResult.class
if (className.endsWith("XResult")) // better replace it with the full name
{
// find, add, remove or rename annotations, fields and methods to your liking here
}
}
return cc.toBytecode();
}
catch (Exception e)
{
// handle error
}
}
In a custom classloader you can override findClass to actually define the modified bytes as XResult instead of the original bytes:
#Override
protected Class<?> findClass(String className) throws ClassNotFoundException
{
// instead of using the original bytes use the modified byte of the class
byte[] classBytes = modifyClass(className);
if (classBytes != null)
{
// this will actually create the Class<XResult> enum representation
return defineClass(className, classBytes, 0, classBytes.length);
}
throw new ClassNotFoundException(...);
}
Instead of a custom classloader you could also use the loader javassist provides:
ClassPool pool = ClassPool.getDefault();
Loader cl = new Loader(pool);
CtClass ct = pool.get("test.Rectangle");
ct.setSuperclass(pool.get("test.Point"));
Class c = cl.loadClass("test.Rectangle");
Object rect = c.newInstance();
Probably you should decompile an enum class and see what an enum class actually contains and what part is you want to get rid of. F.e if the thrown exception does bother you you could simply remove the method and replace it with one that don't throws the exception but returns simply null.
Have a look at the very explanatory tutorials javassist have:
Reading and writing bytecode, ClassPool, ClassLoader
Introspection and customization
Bytecode level API, Generics, Varargs, J2ME
This is how to do it without valueOf in generally O(1):
public enum XStatus {
;
private XStatus() {
XResult xr = Helper.NAMES.get(this.name()); // do whatever with
}
static {
Helper.NAMES = null; // get rid of the map
} // after the constants are instantiated
private static class Helper {
static Map<String, XResult> NAMES = new HashMap<String, XResult>();
static {
for(XResult xr : XResult.values()) {
NAMES.put(xr.name(), xr);
}
}
}
}
Note that this is basically the same as valueOf but you don't have to try-catch. Of course valueOf only throws RuntimeExceptions so you only have to catch if you have constants that do not parallel XResult.
Also, just throwing this out there, a more automatic solution would be something like this:
public final class XStatus {
private XStatus(XResult xr) {
//
}
private static final XStatus[] TABLE; // or an EnumMap
// with Collections.unmodifiableMap
static {
XResult[] xrValues = XResult.values();
TABLE = new XStatus[xrValues.length];
for(XResult xr : xrValues) {
TABLE[xr.ordinal()] = new XStatus(xr);
}
}
public static XStatus xStatusFor(XResult xr) {
return TABLE[xr.ordinal()];
}
}
That will mirror XResult 1:1 without relying on any kind of String evaluation. You need an XResult to retrieve its corresponding constant but that's the point. Also if for some reason XResult were to change outside of your control you don't need to change XStatus.