001/* 002 * Cobertura - http://cobertura.sourceforge.net/ 003 * 004 * Copyright (C) 2011 Piotr Tabor 005 * 006 * Note: This file is dual licensed under the GPL and the Apache 007 * Source License (so that it can be used from both the main 008 * Cobertura classes and the ant tasks). 009 * 010 * Cobertura is free software; you can redistribute it and/or modify 011 * it under the terms of the GNU General Public License as published 012 * by the Free Software Foundation; either version 2 of the License, 013 * or (at your option) any later version. 014 * 015 * Cobertura is distributed in the hope that it will be useful, but 016 * WITHOUT ANY WARRANTY; without even the implied warranty of 017 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 018 * General Public License for more details. 019 * 020 * You should have received a copy of the GNU General Public License 021 * along with Cobertura; if not, write to the Free Software 022 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 023 * USA 024 */ 025 026package net.sourceforge.cobertura.instrument.pass1; 027 028import java.util.Arrays; 029import java.util.LinkedList; 030 031import org.objectweb.asm.Label; 032import org.objectweb.asm.Opcodes; 033 034/** 035 * <p>Represents a single 'footprint' of some piece of ASM code. Is used to detect if two 036 * code pieces are (nearly) the same or different.</p> 037 * 038 * <p>During duplicate-detection we create {@link CodeFootstamp} for every block found starting with LINENUMBER directive.<br/> 039 * We appends to the {@link CodeFootstamp} all found 'jvm asm' instructions. When we found the end of the block (start of next line) we need to 040 * call {@link #finalize()}. After that we are allowed to use {@link #hashCode()}, {@link #equals(Object)} and {@link #isMeaningful()} methods 041 * to compare two blocks and decide if they are duplicates or not. 042 * </p> 043 * 044 * We find two {@link CodeFootstamp} as duplicates not only when they are completely identical, but also if: 045 * <ul> 046 * <li> They start with another number of 'LABEL' instructions (see {@link #trimableIfFirst(String)})</li> 047 * <li> They differs in last instruction being 'LABEL', 'GOTO', 'RETURN' or 'ATHROW' (see {@link #trimableIfLast(String)})</li> 048 * <li> They use different destination labels for JUMPs and SWITCHES</li> 049 * </ul> 050 * 051 * <p> You should also use {@link #isMeaningful()} method to avoid comparing two {@link CodeFootstamp} that are two short (for example empty). 052 * They would be probably found this snapshot as 'equal', but in fact the snapshoots are too short to proof anything. 053 * </p> 054 * 055 * <p>At the implementation level we encode all found instructions into list of String {@link #events} and we are comparing those string list. 056 * It's not beautiful design - but its simple and works. 057 * </p> 058 * 059 * <p>This class implements {@link #equals(Object)} and {@link #hashCode()} so might be used as key in maps</p> 060 */ 061public class CodeFootstamp { 062 private final LinkedList<String> events=new LinkedList<String>(); 063 private boolean finalized=false; 064 065 public void visitLabel(Label label) { 066 appendIfNotFinal("L"); 067 } 068 069 private void appendIfNotFinal(String string) { 070 assertNotFinal(); 071 events.addLast(string); 072 } 073 074 private void assertNotFinal() { 075 if(finalized){ 076 throw new IllegalStateException("The signature has bean already finalized"); 077 } 078 } 079 080 public void visitFieldInsn(int access, String name, String description, String signature) { 081 appendIfNotFinal("F:"+access+":"+name+":"+description+":"+signature); 082 } 083 084 public void visitInsn(int opCode) { 085 appendIfNotFinal(String.valueOf(opCode)); 086 } 087 088 void visitIntInsn(int opCode, int variable) { 089 appendIfNotFinal(opCode+":"+variable); 090 } 091 092 public void visitIintInsn(int opCode, int variable) { 093 appendIfNotFinal(opCode+":"+variable); 094 } 095 096 public void visitLdcInsn(Object obj) { 097 appendIfNotFinal("LDC:"+obj.toString()); 098 } 099 100 public void visitJumpInsn(int opCode, Label label) { 101 appendIfNotFinal("JUMP:"+opCode); 102 } 103 104 public void visitMethodInsn(int opCode, String className, 105 String methodName, String description) { 106 appendIfNotFinal("MI:"+opCode+":"+className+":"+methodName+":"+description); 107 } 108 109 public void visitMultiANewArrayInsn(String type, int arg1) { 110 appendIfNotFinal("MultiArr:"+type+":"+arg1); 111 112 } 113 114 public void visitLookupSwitchInsn(Label arg0, int[] arg1, Label[] arg2) { 115 appendIfNotFinal("LSWITCH:"+Arrays.toString(arg1)); 116 } 117 118 public void visitTableSwitchInsn(int arg0, int arg1, Label arg2, Label[] arg3) { 119 appendIfNotFinal("TSWITCH:"+arg0+":"+arg1); 120 } 121 122 public void finalize(){ 123 while(events.size()>0 && trimableIfLast(events.getLast())){ 124 events.removeLast(); 125 } 126 while(events.size()>0 && trimableIfFirst(events.getFirst())){ 127 events.removeFirst(); 128 } 129 finalized=true; 130 } 131 132 private boolean trimableIfFirst(String e) { 133 return e.equals("L");//No labels at the begining 134 } 135 136 private boolean trimableIfLast(String e) { 137 return e.equals("JUMP:"+String.valueOf(Opcodes.GOTO))|| 138 e.equals(String.valueOf(Opcodes.RETURN))|| 139 e.equals(String.valueOf(Opcodes.ATHROW))|| 140 e.equals("L"); 141 } 142 143 @Override 144 public String toString() { 145 StringBuffer sb=new StringBuffer(); 146 for(String s:events){ 147 sb.append(s).append(';'); 148 } 149 return sb.toString(); 150 } 151 152 @Override 153 public int hashCode() { 154 final int prime = 31; 155 int result = 1; 156 result = prime * result + ((events == null) ? 0 : events.hashCode()); 157 return result; 158 } 159 160 @Override 161 public boolean equals(Object obj) { 162 if (this == obj) { 163 return true; 164 } 165 if (obj == null) { 166 return false; 167 } 168 if (getClass() != obj.getClass()) { 169 return false; 170 } 171 CodeFootstamp other = (CodeFootstamp) obj; 172 if (events == null) { 173 if (other.events != null) { 174 return false; 175 } 176 } else if (!events.equals(other.events)) { 177 return false; 178 } 179 return true; 180 } 181 182 /** 183 * Some signatures are to simple (empty) and generates false positive duplicates. To avoid 184 * that we filter here lines that are shorter then 2 jvm asm instruction. 185 * 186 * @return true if the signature is long enough to make sense comparing it 187 */ 188 public boolean isMeaningful() { 189 if (!finalized){ 190 throw new IllegalStateException("The signature should been already finalized"); 191 } 192 return events.size()>=1; 193 } 194 195}