package com.jreframeworker.engine; import java.util.Iterator; import java.util.LinkedList; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.commons.RemappingMethodAdapter; import org.objectweb.asm.commons.SimpleRemapper; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.FieldInsnNode; import org.objectweb.asm.tree.FieldNode; import org.objectweb.asm.tree.FrameNode; import org.objectweb.asm.tree.IincInsnNode; import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.IntInsnNode; import org.objectweb.asm.tree.InvokeDynamicInsnNode; import org.objectweb.asm.tree.JumpInsnNode; import org.objectweb.asm.tree.LabelNode; import org.objectweb.asm.tree.LdcInsnNode; import org.objectweb.asm.tree.LineNumberNode; import org.objectweb.asm.tree.LookupSwitchInsnNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.MultiANewArrayInsnNode; import org.objectweb.asm.tree.TableSwitchInsnNode; import org.objectweb.asm.tree.TypeInsnNode; import org.objectweb.asm.tree.VarInsnNode; import com.jreframeworker.engine.identifiers.JREFAnnotationIdentifier; import com.jreframeworker.engine.log.Log; import com.jreframeworker.engine.utils.AnnotationUtils; /** * This class is responsible for merging two class files based on * the merging strategies in the JReFrameworker framework. * * References: http://www.jroller.com/eu/entry/merging_class_methods_with_asm * * @author Ben Holland */ @SuppressWarnings("deprecation") public class MergeAdapter extends ClassVisitor { private ClassNode classToMerge; private String baseClassName; private String mergeRenamePrefix; private LinkedList<String> qualifiedRenamedMethods; public MergeAdapter(ClassVisitor baseClassVisitor, ClassNode classToMerge, String mergeReamePrefix, LinkedList<String> qualifiedRenamedMethods) { super(Opcodes.ASM5, baseClassVisitor); this.classToMerge = classToMerge; this.mergeRenamePrefix = mergeReamePrefix; this.qualifiedRenamedMethods = qualifiedRenamedMethods; } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { super.visit(version, access, name, signature, superName, interfaces); baseClassName = name; } public void visitEnd() { // copy each field of the class to merge in to the original class for (Object fieldObject : classToMerge.fields) { FieldNode fieldNode = (FieldNode) fieldObject; // only insert the field if it is annotated if(fieldNode.invisibleAnnotations != null){ boolean addField = false; for(Object annotationObject : fieldNode.invisibleAnnotations){ AnnotationNode annotationNode = (AnnotationNode) annotationObject; JREFAnnotationIdentifier checker = new JREFAnnotationIdentifier(); checker.visitAnnotation(annotationNode.desc, false); if(checker.isDefineFieldAnnotation()){ addField = true; break; } } if(addField){ // clear field annotations and insert the field AnnotationUtils.clearFieldAnnotations(fieldNode); fieldNode.accept(this); Log.info("Added Field: " + fieldNode.name); } } } // copy each method of the class to merge that is annotated // with a jref annotation to the original class for (Object methodObject : classToMerge.methods) { MethodNode methodNodeToMerge = (MethodNode) methodObject; // static initializers need to be handled specially if(methodNodeToMerge.name.equals("<clinit>")){ // TODO: merge static initializers } else if(methodNodeToMerge.name.equals("<init>")){ // TODO: merge initializers } else { boolean define = false; boolean merge = false; // check if method is annotated with a jref annotation LinkedList<AnnotationNode> jrefAnnotations = new LinkedList<AnnotationNode>(); if (methodNodeToMerge.invisibleAnnotations != null) { for (Object annotationObject : methodNodeToMerge.invisibleAnnotations) { AnnotationNode annotation = (AnnotationNode) annotationObject; // check if the annotation is a jref annotation JREFAnnotationIdentifier jrefChecker = new JREFAnnotationIdentifier(); jrefChecker.visitAnnotation(annotation.desc, false); if(jrefChecker.isJREFAnnotation()){ jrefAnnotations.add(annotation); if(jrefChecker.isDefineMethodAnnotation()){ define = true; } if(jrefChecker.isMergeMethodAnnotation()){ merge = true; } } } } // if the method is annotated with @DefineMethod or @MergeMethod, add the method if(define || merge){ // in any case, strip the jref annotations from the method methodNodeToMerge.invisibleAnnotations.removeAll(jrefAnnotations); if(merge){ mergeMethod(methodNodeToMerge, qualifiedRenamedMethods); Log.info("Merged Method: " + methodNodeToMerge.name); } else { addMethod(methodNodeToMerge); Log.info("Added Method: " + methodNodeToMerge.name); } } } } super.visitEnd(); } /** * Adds the method to the base class * @param methodNode */ private void addMethod(MethodNode methodNode){ String[] exceptions = new String[methodNode.exceptions.size()]; methodNode.exceptions.toArray(exceptions); MethodVisitor mv = cv.visitMethod(methodNode.access, methodNode.name, methodNode.desc, methodNode.signature, exceptions); methodNode.instructions.resetLabels(); // SimpleRemapper -> maps old name to new name // updates owners and descriptions appropriately methodNode.accept(new RemappingMethodAdapter(methodNode.access, methodNode.desc, mv, new SimpleRemapper(classToMerge.name, baseClassName))); } /** * Performs some merge changes to the method instructions then adds the method * @param methodNode * @param qualifiedRenamedMethods */ @SuppressWarnings("unused") private void mergeMethod(MethodNode methodNode, LinkedList<String> qualifiedRenamedMethods) { // clean up method instructions InsnList instructions = methodNode.instructions; Iterator<AbstractInsnNode> instructionIterator = instructions.iterator(); while (instructionIterator.hasNext()) { AbstractInsnNode abstractInstruction = instructionIterator.next(); if (abstractInstruction instanceof FieldInsnNode) { FieldInsnNode instruction = (FieldInsnNode) abstractInstruction; } else if (abstractInstruction instanceof FrameNode) { FrameNode instruction = (FrameNode) abstractInstruction; } else if (abstractInstruction instanceof IincInsnNode) { IincInsnNode instruction = (IincInsnNode) abstractInstruction; } else if (abstractInstruction instanceof InsnNode) { InsnNode instruction = (InsnNode) abstractInstruction; } else if (abstractInstruction instanceof IntInsnNode) { IntInsnNode instruction = (IntInsnNode) abstractInstruction; } else if (abstractInstruction instanceof InvokeDynamicInsnNode) { InvokeDynamicInsnNode instruction = (InvokeDynamicInsnNode) abstractInstruction; } else if (abstractInstruction instanceof JumpInsnNode) { JumpInsnNode instruction = (JumpInsnNode) abstractInstruction; } else if (abstractInstruction instanceof LabelNode) { LabelNode instruction = (LabelNode) abstractInstruction; } else if (abstractInstruction instanceof LdcInsnNode) { LdcInsnNode instruction = (LdcInsnNode) abstractInstruction; } else if (abstractInstruction instanceof LineNumberNode) { LineNumberNode instruction = (LineNumberNode) abstractInstruction; } else if (abstractInstruction instanceof LookupSwitchInsnNode) { LookupSwitchInsnNode instruction = (LookupSwitchInsnNode) abstractInstruction; } else if (abstractInstruction instanceof MethodInsnNode) { MethodInsnNode instruction = (MethodInsnNode) abstractInstruction; // check if the method call needs to be changed to a renamed method name // replace calls to super.x methods with prefix+x calls in the class to merge // TODO: should check more than just the name, need to check whole method signature for (String renamedMethod : qualifiedRenamedMethods) { String qualifiedMethodName = instruction.owner + "." + instruction.name; if ((qualifiedMethodName).equals(renamedMethod)) { // this method has been renamed, we need to rename the call as well instruction.name = mergeRenamePrefix + instruction.name; // if the renamed method was a special invocation then we were // calling the preserved method using super.foo(), so we need // to make it a virtual invocation instead of special invocation if (instruction.getOpcode() == Opcodes.INVOKESPECIAL) { instruction.setOpcode(Opcodes.INVOKEVIRTUAL); } } // if the renamed method was a static invocation // and the static invocation is to the class being merged in // then we are calling the new static method so we need to change the owner if (instruction.getOpcode() == Opcodes.INVOKESTATIC) { if(classToMerge.name.equals(instruction.owner) && instruction.name.equals(renamedMethod)){ instruction.owner = baseClassName; } } } } else if (abstractInstruction instanceof MultiANewArrayInsnNode) { MultiANewArrayInsnNode instruction = (MultiANewArrayInsnNode) abstractInstruction; } else if (abstractInstruction instanceof TableSwitchInsnNode) { TableSwitchInsnNode instruction = (TableSwitchInsnNode) abstractInstruction; } else if (abstractInstruction instanceof TypeInsnNode) { TypeInsnNode instruction = (TypeInsnNode) abstractInstruction; } else if (abstractInstruction instanceof VarInsnNode) { VarInsnNode instruction = (VarInsnNode) abstractInstruction; } } // finally insert the method addMethod(methodNode); } }