Java tutorial
/* * Copyright 2010-2012 VMware and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springsource.loaded; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import org.objectweb.asm.ClassReader; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; 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.IincInsnNode; import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.IntInsnNode; 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; /** * Compute the differences between two versions of a type as a series of deltas. Entry point is the computeDifferences method. * * @author Andy Clement * @since 0.5.0 */ public class TypeDiffComputer implements Opcodes { public static TypeDelta computeDifferences(byte[] oldbytes, byte[] newbytes) { ClassNode oldClassNode = new ClassNode(); new ClassReader(oldbytes).accept(oldClassNode, 0); ClassNode newClassNode = new ClassNode(); new ClassReader(newbytes).accept(newClassNode, 0); TypeDelta delta = computeDelta(oldClassNode, newClassNode); return delta; } private static TypeDelta computeDelta(ClassNode oldClassNode, ClassNode newClassNode) { // The type itself: (int version, int access, String name, String signature, String superName, String[] interfaces) { TypeDelta td = new TypeDelta(); computeTypeDelta(oldClassNode, newClassNode, td); computeFieldDelta(oldClassNode, newClassNode, td); computeMethodDelta(oldClassNode, newClassNode, td); // TODO delta: implement the rest of computeDelta. These methods from ClassVisitor should help in knowing what is left to do: // public void visitSource(String source, String debug) { // public void visitOuterClass(String owner, String name, String desc) { // public AnnotationVisitor visitAnnotation(String desc, boolean visible) { // public void visitAttribute(Attribute attr) { // public void visitInnerClass(String name, String outerName, String innerName, int access) { // public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { // public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { // public void visitEnd() { return td; } @SuppressWarnings("unchecked") private static void computeMethodDelta(ClassNode oldClassNode, ClassNode newClassNode, TypeDelta td) { List<MethodNode> nMethods = newClassNode.methods; List<MethodNode> oMethods = new ArrayList<MethodNode>(oldClassNode.methods); // Going through the new methods and comparing them to the old if (nMethods != null) { for (MethodNode nMethod : nMethods) { MethodNode found = null; for (MethodNode oMethod : oMethods) { if (oMethod.name.equals(nMethod.name) && oMethod.desc.equals(nMethod.desc)) { // TODO modifiers compared? found = oMethod; computeAnyMethodDifferences(oMethod, nMethod, td); } } if (found == null) { td.addNewMethod(nMethod); } else { oMethods.remove(found); } } } for (MethodNode lostMethod : oMethods) { td.addLostMethod(lostMethod); } } @SuppressWarnings("unchecked") private static void computeFieldDelta(ClassNode oldClassNode, ClassNode newClassNode, TypeDelta td) { // int oSize = oldClassNode.fields.size(); int nSize = newClassNode.fields.size(); // Take a copy as we are going to delete entries in the next loop List<FieldNode> oFields = new ArrayList<FieldNode>(oldClassNode.fields); // Going through the new fields comparing them to the old for (int n = 0; n < nSize; n++) { FieldNode nField = (FieldNode) newClassNode.fields.get(n); FieldNode found = null; for (FieldNode oField : oFields) { if (oField.name.equals(nField.name)) { // found it! found = oField; // is it exactly the same? computeAnyFieldDifferences(oField, nField, td); } } if (found == null) { // this is a new field td.addNewField(nField); } else { oFields.remove(found); } } // Those left in oFields were not in nFields so have been removed! for (FieldNode lostField : oFields) { td.addLostField(lostField); } } /** * Check the properties of the field - if they have changed at all then record what kind of change for the field. Thinking the * type delta should have a map from names to a delta describing (capturing) the change. */ @SuppressWarnings("unchecked") private static void computeAnyFieldDifferences(FieldNode oField, FieldNode nField, TypeDelta td) { // Want to record things that are different between these two fields... FieldDelta fd = new FieldDelta(oField.name); if (oField.access != nField.access) { // access changed fd.setAccessChanged(oField.access, nField.access); } if (!oField.desc.equals(nField.desc)) { // type changed fd.setTypeChanged(oField.desc, nField.desc); } String annotationChange = compareAnnotations(oField.invisibleAnnotations, nField.invisibleAnnotations); annotationChange = annotationChange + compareAnnotations(oField.visibleAnnotations, nField.visibleAnnotations); if (annotationChange.length() != 0) { fd.setAnnotationsChanged(annotationChange); } if (fd.hasAnyChanges()) { // it needs recording td.addChangedField(fd); } } /** * Determine if there any differences between the methods supplied. A MethodDelta object is built to record any differences and * stored against the type delta. * * @param oMethod 'old' method * @param nMethod 'new' method * @param td the type delta where changes are currently being accumulated */ private static void computeAnyMethodDifferences(MethodNode oMethod, MethodNode nMethod, TypeDelta td) { MethodDelta md = new MethodDelta(oMethod.name, oMethod.desc); if (oMethod.access != nMethod.access) { md.setAccessChanged(oMethod.access, nMethod.access); } // TODO annotations InsnList oInstructions = oMethod.instructions; InsnList nInstructions = nMethod.instructions; if (oInstructions.size() != nInstructions.size()) { md.setInstructionsChanged(oInstructions.toArray(), nInstructions.toArray()); } else { // TODO Just interested in constructors right now - should add others if (oMethod.name.charAt(0) == '<') { String oInvokeSpecialDescriptor = null; String nInvokeSpecialDescriptor = null; int oUninitCount = 0; int nUninitCount = 0; boolean codeChange = false; for (int i = 0, max = oInstructions.size(); i < max; i++) { AbstractInsnNode oInstruction = oInstructions.get(i); AbstractInsnNode nInstruction = nInstructions.get(i); if (!codeChange) { if (!sameInstruction(oInstruction, nInstruction)) { codeChange = true; } } if (oInstruction.getType() == AbstractInsnNode.TYPE_INSN) { if (oInstruction.getOpcode() == Opcodes.NEW) { oUninitCount++; } } if (nInstruction.getType() == AbstractInsnNode.TYPE_INSN) { if (nInstruction.getOpcode() == Opcodes.NEW) { nUninitCount++; } } if (oInstruction.getType() == AbstractInsnNode.METHOD_INSN) { MethodInsnNode mi = (MethodInsnNode) oInstruction; if (mi.getOpcode() == INVOKESPECIAL && mi.name.equals("<init>")) { if (oUninitCount == 0) { // this is the one! oInvokeSpecialDescriptor = mi.desc; } else { oUninitCount--; } } } if (nInstruction.getType() == AbstractInsnNode.METHOD_INSN) { MethodInsnNode mi = (MethodInsnNode) nInstruction; if (mi.getOpcode() == INVOKESPECIAL && mi.name.equals("<init>")) { if (nUninitCount == 0) { // this is the one! nInvokeSpecialDescriptor = mi.desc; } else { nUninitCount--; } } } } // Has the invokespecial changed? if (oInvokeSpecialDescriptor == null) { if (nInvokeSpecialDescriptor != null) { md.setInvokespecialChanged(oInvokeSpecialDescriptor, nInvokeSpecialDescriptor); } } else { if (!oInvokeSpecialDescriptor.equals(nInvokeSpecialDescriptor)) { md.setInvokespecialChanged(oInvokeSpecialDescriptor, nInvokeSpecialDescriptor); } } if (codeChange) { md.setCodeChanged(oInstructions.toArray(), nInstructions.toArray()); } } } if (md.hasAnyChanges()) { // it needs recording td.addChangedMethod(md); } } private static boolean sameInstruction(AbstractInsnNode o, AbstractInsnNode n) { if (o.getType() != o.getType() || o.getOpcode() != n.getOpcode()) { return false; } switch (o.getType()) { case (AbstractInsnNode.INSN): // 0 if (!sameInsnNode(o, n)) { return false; } break; case (AbstractInsnNode.INT_INSN): // 1 if (!sameIntInsnNode(o, n)) { return false; } break; case (AbstractInsnNode.VAR_INSN): // 2 if (!sameVarInsn(o, n)) { return false; } break; case (AbstractInsnNode.TYPE_INSN):// 3 if (!sameTypeInsn(o, n)) { return false; } break; case (AbstractInsnNode.FIELD_INSN): // 4 if (!sameFieldInsn(o, n)) { return false; } break; case (AbstractInsnNode.METHOD_INSN): // 5 if (!sameMethodInsnNode(o, n)) { return false; } break; case (AbstractInsnNode.JUMP_INSN): // 6 if (!sameJumpInsnNode(o, n)) { return false; } break; case (AbstractInsnNode.LABEL): // 7 if (!sameLabelNode(o, n)) { return false; } break; case (AbstractInsnNode.LDC_INSN): // 8 if (!sameLdcInsnNode(o, n)) { return false; } break; case (AbstractInsnNode.IINC_INSN): // 9 if (!sameIincInsn(o, n)) { return false; } break; case (AbstractInsnNode.TABLESWITCH_INSN): // 10 if (!sameTableSwitchInsn(o, n)) { return false; } break; case (AbstractInsnNode.LOOKUPSWITCH_INSN): // 11 if (!sameLookupSwitchInsn(o, n)) { return false; } break; case (AbstractInsnNode.MULTIANEWARRAY_INSN): // 12 if (!sameMultiANewArrayInsn(o, n)) { return false; } break; case (AbstractInsnNode.FRAME): // 13 if (!sameFrameInsn(o, n)) { return false; } break; case (AbstractInsnNode.LINE): // 14 if (!sameLineNumberNode(o, n)) { return false; } break; default: throw new IllegalStateException("nyi " + o.getType()); } return true; } private static boolean sameFrameInsn(AbstractInsnNode o, AbstractInsnNode n) { // given that these nodes are computed based on everything else. if everything else is the same then these // must be the same. A full comparison could be a little ugly as different frames can be equivalent (maybe // the compiler produces an incremental frame on one run then a full frame on the next). return true; } private static boolean sameMultiANewArrayInsn(AbstractInsnNode o, AbstractInsnNode n) { if (!(n instanceof MultiANewArrayInsnNode)) { return false; } MultiANewArrayInsnNode mnao = (MultiANewArrayInsnNode) o; MultiANewArrayInsnNode mnan = (MultiANewArrayInsnNode) n; if (!mnao.desc.equals(mnan.desc)) { return false; } if (mnao.dims != mnan.dims) { return false; } return true; } @SuppressWarnings("unchecked") private static boolean sameLookupSwitchInsn(AbstractInsnNode o, AbstractInsnNode n) { if (!(n instanceof LookupSwitchInsnNode)) { return false; } LookupSwitchInsnNode lsio = (LookupSwitchInsnNode) o; LookupSwitchInsnNode lsin = (LookupSwitchInsnNode) n; if (sameLabels(lsio.dflt, lsin.dflt)) { return false; } List<Integer> keyso = lsio.keys; List<Integer> keysn = lsin.keys; if (keyso.size() != keysn.size()) { return false; } for (int i = 0, max = keyso.size(); i < max; i++) { if (keyso.get(i) != keysn.get(i)) { return false; } } List<LabelNode> labelso = lsio.labels; List<LabelNode> labelsn = lsin.labels; if (labelso.size() != labelsn.size()) { return false; } for (int i = 0, max = labelso.size(); i < max; i++) { if (!sameLabelNode(labelso.get(i), labelsn.get(i))) { return false; } } return true; } @SuppressWarnings("unchecked") private static boolean sameTableSwitchInsn(AbstractInsnNode o, AbstractInsnNode n) { if (!(n instanceof TableSwitchInsnNode)) { return false; } TableSwitchInsnNode tsio = (TableSwitchInsnNode) o; TableSwitchInsnNode tsin = (TableSwitchInsnNode) n; if (sameLabels(tsio.dflt, tsin.dflt)) { return false; } if (tsio.min != tsin.min) { return false; } if (tsio.max != tsin.max) { return false; } List<LabelNode> labelso = tsio.labels; List<LabelNode> labelsn = tsin.labels; if (labelso.size() != labelsn.size()) { return false; } for (int i = 0, max = labelso.size(); i < max; i++) { if (!sameLabelNode(labelso.get(i), labelsn.get(i))) { return false; } } return true; } private static boolean sameLabels(LabelNode lno, LabelNode lnn) { // TODO implement? return false; } private static boolean sameFieldInsn(AbstractInsnNode o, AbstractInsnNode n) { FieldInsnNode oi = (FieldInsnNode) o; if (!(n instanceof FieldInsnNode)) { return false; } FieldInsnNode ni = (FieldInsnNode) n; return oi.name.equals(ni.name) && oi.desc.equals(ni.desc) && oi.owner.equals(ni.owner); } private static boolean sameMethodInsnNode(AbstractInsnNode o, AbstractInsnNode n) { MethodInsnNode oi = (MethodInsnNode) o; if (!(n instanceof MethodInsnNode)) { return false; } MethodInsnNode ni = (MethodInsnNode) n; return oi.name.equals(ni.name) && oi.desc.equals(ni.desc) && oi.owner.equals(ni.owner); } private static boolean sameVarInsn(AbstractInsnNode o, AbstractInsnNode n) { VarInsnNode oi = (VarInsnNode) o; if (!(n instanceof VarInsnNode)) { return false; } VarInsnNode ni = (VarInsnNode) n; return oi.var == ni.var; } private static boolean sameInsnNode(AbstractInsnNode o, AbstractInsnNode n) { InsnNode oi = (InsnNode) o; if (!(n instanceof InsnNode)) { return false; } InsnNode ni = (InsnNode) n; return oi.getOpcode() == ni.getOpcode(); } private static boolean sameJumpInsnNode(AbstractInsnNode o, AbstractInsnNode n) { // JumpInsnNode oJumpInsnNode = (JumpInsnNode) o; if (!(n instanceof JumpInsnNode)) { return false; } // JumpInsnNode nJumpInsnNode = (JumpInsnNode) n; // TODO tricky to compare destinations when captured as labels with no exposed identifier/position return true; } private static boolean sameLdcInsnNode(AbstractInsnNode o, AbstractInsnNode n) { LdcInsnNode oi = (LdcInsnNode) o; if (!(n instanceof LdcInsnNode)) { return false; } LdcInsnNode ni = (LdcInsnNode) n; Object ocst = oi.cst; if (ocst instanceof Integer) { if (!(ni.cst instanceof Integer)) { return false; } return ((Integer) ocst).equals(ni.cst); } if (ocst instanceof Float) { if (!(ni.cst instanceof Float)) { return false; } return ((Float) ocst).equals(ni.cst); } if (ocst instanceof Long) { if (!(ni.cst instanceof Long)) { return false; } return ((Long) ocst).equals(ni.cst); } if (ocst instanceof Double) { if (!(ni.cst instanceof Double)) { return false; } return ((Double) ocst).equals(ni.cst); } if (ocst instanceof String) { if (!(ni.cst instanceof String)) { return false; } return ((String) ocst).equals(ni.cst); } // must be Type return ((Type) ocst).equals(ni.cst); } private static boolean sameIntInsnNode(AbstractInsnNode o, AbstractInsnNode n) { IntInsnNode oi = (IntInsnNode) o; if (!(n instanceof IntInsnNode)) { return false; } IntInsnNode ni = (IntInsnNode) n; return oi.operand == ni.operand; } private static boolean sameLineNumberNode(AbstractInsnNode o, AbstractInsnNode n) { LineNumberNode oi = (LineNumberNode) o; if (!(n instanceof LineNumberNode)) { return false; } LineNumberNode ni = (LineNumberNode) n; return oi.line == ni.line; // TODO check oi.start? } private static boolean sameIincInsn(AbstractInsnNode o, AbstractInsnNode n) { IincInsnNode oi = (IincInsnNode) o; if (!(n instanceof IincInsnNode)) { return false; } IincInsnNode ni = (IincInsnNode) n; return oi.var == ni.var && oi.incr == ni.incr; } private static boolean sameTypeInsn(AbstractInsnNode o, AbstractInsnNode n) { TypeInsnNode oi = (TypeInsnNode) o; if (!(n instanceof TypeInsnNode)) { return false; } TypeInsnNode ni = (TypeInsnNode) n; return oi.desc.equals(ni.desc); } /** * Compare two labels to check they are the same. * * @param o 'old' label * @param n 'new' label * @return true if they are different */ private static boolean sameLabelNode(AbstractInsnNode o, AbstractInsnNode n) { // LabelNode oi = (LabelNode) o; if (!(n instanceof LabelNode)) { return false; } // LabelNode ni = (LabelNode) n; // TODO tricky to get right. Unfortunately the positions aren't always available - and we can't check if they are, we have to call the // getOffset() method on label and catch an exception if they aren't return true; } private static String compareAnnotations(List<AnnotationNode> oldAnnos, List<AnnotationNode> newAnnos) { if (oldAnnos == null) { if (newAnnos == null) { return ""; } oldAnnos = Collections.emptyList(); } if (newAnnos == null) { newAnnos = Collections.emptyList(); } StringBuilder diff = new StringBuilder(); // Which have been removed for (AnnotationNode o : oldAnnos) { boolean found = false; String oFormatted = Utils.annotationNodeFormat(o); for (AnnotationNode n : newAnnos) { String nFormatted = Utils.annotationNodeFormat(n); if (oFormatted.equals(nFormatted)) { found = true; break; } } if (!found) { diff.append("-").append(oFormatted); } } // Which have been added for (AnnotationNode n : newAnnos) { boolean found = false; String nFormatted = Utils.annotationNodeFormat(n); for (AnnotationNode o : oldAnnos) { String oFormatted = Utils.annotationNodeFormat(o); if (oFormatted.equals(nFormatted)) { found = true; break; } } if (!found) { diff.append("+").append(nFormatted); } } return diff.toString(); } @SuppressWarnings("unchecked") private static void computeTypeDelta(ClassNode oldClassNode, ClassNode newClassNode, TypeDelta td) { // if (oldClassNode.version != newClassNode.version) { // td.setTypeVersionChange(oldClassNode.version, newClassNode.version); // } if (oldClassNode.access != newClassNode.access) { // Is it only because of 0x20000 - that appears to represent Deprecated! if ((oldClassNode.access & 0xffff) != (newClassNode.access & 0xffff)) { td.setTypeAccessChange(oldClassNode.access, newClassNode.access); } } if (!oldClassNode.name.equals(newClassNode.name)) { td.setTypeNameChange(oldClassNode.name, newClassNode.name); } // if (oldClassNode.signature == null) { // if (newClassNode.signature != null) { // td.setTypeSignatureChange(oldClassNode.signature, newClassNode.signature); // } // } else if (newClassNode.signature == null) { // if (oldClassNode.signature != null) { // td.setTypeSignatureChange(oldClassNode.signature, newClassNode.signature); // } // } else if (!oldClassNode.signature.equals(newClassNode.signature)) { // td.setTypeSignatureChange(oldClassNode.signature, newClassNode.signature); // } if (oldClassNode.superName == null) { if (newClassNode.superName != null) { td.setTypeSuperNameChange(oldClassNode.superName, newClassNode.superName); } } else if (newClassNode.superName == null) { if (oldClassNode.superName != null) { td.setTypeSuperNameChange(oldClassNode.superName, newClassNode.superName); } } else if (!oldClassNode.superName.equals(newClassNode.superName)) { td.setTypeSuperNameChange(oldClassNode.superName, newClassNode.superName); } if (oldClassNode.interfaces.size() == 0) { if (newClassNode.interfaces.size() != 0) { td.setTypeInterfacesChange(oldClassNode.interfaces, newClassNode.interfaces); } } else if (newClassNode.interfaces.size() == 0) { if (oldClassNode.interfaces.size() != 0) { td.setTypeInterfacesChange(oldClassNode.interfaces, newClassNode.interfaces); } } else { if (oldClassNode.interfaces.size() != newClassNode.interfaces.size()) { td.setTypeInterfacesChange(oldClassNode.interfaces, newClassNode.interfaces); } HashSet<String> oldInterfaceSet = new HashSet<String>(oldClassNode.interfaces); HashSet<String> newInterfaceSet = new HashSet<String>(newClassNode.interfaces); if (!oldInterfaceSet.equals(newInterfaceSet)) { // TODO expensive? keep the interfaces list sorted instead? td.setTypeInterfacesChange(oldClassNode.interfaces, newClassNode.interfaces); } } } }