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; 027 028import java.util.Collection; 029import java.util.Collections; 030import java.util.HashMap; 031import java.util.LinkedList; 032import java.util.List; 033import java.util.Map; 034import java.util.concurrent.atomic.AtomicInteger; 035import java.util.regex.Pattern; 036 037import net.sourceforge.cobertura.util.RegexUtil; 038 039import org.objectweb.asm.Label; 040import org.objectweb.asm.MethodVisitor; 041import org.objectweb.asm.Opcodes; 042import org.objectweb.asm.tree.AbstractInsnNode; 043import org.objectweb.asm.tree.InsnNode; 044import org.objectweb.asm.tree.MethodInsnNode; 045import org.objectweb.asm.tree.VarInsnNode; 046 047/** 048 * Analyzes given method, assign unique event identifiers to every found 049 * interesting instruction and calls business method in the {@link #touchPointListener}. 050 * 051 * @author ptab 052 */ 053public class FindTouchPointsMethodAdapter extends ContextMethodAwareMethodAdapter { 054 /** 055 * Source of identifiers for events. 056 * 057 * <p>Remember to acquire identifiers using {@link AtomicInteger#incrementAndGet()} (not {@link AtomicInteger#getAndIncrement()}!!!)</p> 058 */ 059 private final AtomicInteger eventIdGenerator; 060 061 /** 062 * Backing listener that will be informed about all interesting events found 063 */ 064 private TouchPointListener touchPointListener; 065 066 /** 067 * Line number of current line. 068 * 069 * <p>It it NOT lineId</pl> 070 */ 071 private int currentLine; 072 073 /** 074 * List of patterns to know that we don't want trace lines that are calls to some methods 075 */ 076 private Collection<Pattern> ignoreRegexp; 077 078 /** 079 * See {@link AbstractFindTouchPointsClassInstrumenter#duplicatedLinesMap} 080 */ 081 private final Map<Integer, Map<Integer, Integer>> duplicatedLinesMap; 082 083 /** 084 * Map of (line number -> (lineId -> List of eventIds)). 085 * 086 * <p>For every line number, and for evere lineId in the line we store list of all generated events</p> 087 * 088 * <p>When we will detect duplicated block of code in given line - instead of generating new eventIds we will 089 * use the same events</p> 090 */ 091 private final Map<Integer, Map<Integer, LinkedList<Integer>>> line2eventIds = new HashMap<Integer, Map<Integer, LinkedList<Integer>>>(); 092 093 /** 094 * If we are currently processing a new (not duplicated line), it is a list (linked into {@link #line2eventIds}) that we use to store newly generated identifiers into it, 095 * otherwise it is null. 096 */ 097 private LinkedList<Integer> saveEventIdList = null; 098 099 /** 100 * If we are currently processing a duplicated line, it is a list of identifiers that should be used for the line. After processing event, you should remove identifier from the begining of the list. 101 */ 102 private LinkedList<Integer> replyEventIdList = null; 103 104 /** 105 * State of last N instructions. 106 */ 107 private final List<AbstractInsnNode> backlog; 108 109 public FindTouchPointsMethodAdapter(HistoryMethodAdapter mv, 110 String className, String methodName, String methodSignature, 111 AtomicInteger eventIdGenerator, 112 Map<Integer, Map<Integer, Integer>> duplicatedLinesMap, 113 AtomicInteger lineIdGenerator) { 114 this(mv, mv.backlog(), className, methodName, methodSignature, eventIdGenerator, duplicatedLinesMap, lineIdGenerator); 115 } 116 117 public FindTouchPointsMethodAdapter(MethodVisitor mv, 118 String className, String methodName, String methodSignature, 119 AtomicInteger eventIdGenerator, 120 Map<Integer, Map<Integer, Integer>> duplicatedLinesMap, 121 AtomicInteger lineIdGenerator) { 122 this(mv, Collections. <AbstractInsnNode> emptyList(), 123 className, methodName, methodSignature, eventIdGenerator, duplicatedLinesMap, lineIdGenerator); 124 } 125 126 protected FindTouchPointsMethodAdapter(MethodVisitor mv, 127 List<AbstractInsnNode> backlog, 128 String className, String methodName, String methodSignature, 129 AtomicInteger eventIdGenerator, 130 Map<Integer, Map<Integer, Integer>> duplicatedLinesMap, 131 AtomicInteger lineIdGenerator) { 132 super(mv, className, methodName, methodSignature, lineIdGenerator); 133 this.backlog = backlog; 134 this.eventIdGenerator = eventIdGenerator; 135 this.duplicatedLinesMap = duplicatedLinesMap; 136 } 137 138 private int generateNewEventId() { 139 return eventIdGenerator.incrementAndGet(); 140 } 141 142 /** 143 * Depending on situation if we are processing a new line or duplicated line, 144 * generates a new identifier or reuses previously generated for the same event. 145 * @return 146 */ 147 private int getEventId() { 148 if (replyEventIdList != null && !replyEventIdList.isEmpty()) { 149 //in case of a duplicated line 150 return replyEventIdList.removeFirst(); 151 } else { 152 // in case of a new line 153 int eventId = generateNewEventId(); 154 if (saveEventIdList != null) { 155 saveEventIdList.addLast(eventId); 156 } 157 return eventId; 158 } 159 } 160 161 @Override 162 public void visitCode() { 163 super.visitCode(); 164 touchPointListener.afterMethodStart(mv); 165 } 166 167 /** 168 * Processing information about new line. 169 * 170 * Upgrades {@link #replyEventIdList} and {@link #saveEventIdList} and calls {@link TouchPointListener#afterLineNumber(int, Label, int, MethodVisitor, String, String)} 171 */ 172 public void visitLineNumber(int line, Label label) { 173 super.visitLineNumber(line, label); 174 currentLine = line; 175 176 if (!isDuplicatedLine(line, lastLineId)) { 177 /* 178 * It is a new line (first time seen, so we will save all found 179 * events) 180 */ 181 replyEventIdList = null; 182 saveEventIdList = new LinkedList<Integer>(); 183 Map<Integer, LinkedList<Integer>> eventsMap = line2eventIds 184 .get(line); 185 if (eventsMap == null) { 186 eventsMap = new HashMap<Integer, LinkedList<Integer>>(); 187 line2eventIds.put(line, eventsMap); 188 } 189 eventsMap.put(lastLineId, saveEventIdList); 190 } else { 191 Integer orgin = getOriginForLine(line, lastLineId); 192 Map<Integer, LinkedList<Integer>> m = line2eventIds .get(currentLine); 193 LinkedList<Integer> eventIds = m.get(orgin); 194 195 /* copy of current list */ 196 replyEventIdList = new LinkedList<Integer>(eventIds); 197 saveEventIdList = null; 198 } 199 200 touchPointListener.afterLineNumber(getEventId(), label, currentLine, 201 mv, methodName, methodSignature); 202 } 203 204 /** 205 * Checks if given line is a duplicate of previously processed line 206 * 207 * @param line - line number 208 * @param lineId - line identifier 209 * @return true - if line is duplicate of previously processed lines 210 */ 211 private boolean isDuplicatedLine(int line, Integer lineId) { 212 return getOriginForLine(line, lineId) != null; 213 } 214 215 /** 216 * @param line - line number 217 * @param lineId - line identifier 218 * 219 * @return lineId of the origin line (first processed line) that the given line is duplicate of. 220 * If the current line is not an duplicate of the previously processed line the method returns NULL. 221 */ 222 private Integer getOriginForLine(int line, Integer lineId) { 223 Map<Integer, Integer> labelMap = duplicatedLinesMap.get(line); 224 return labelMap != null ? labelMap.get(lineId) : null; 225 } 226 227 @Override 228 public void visitLabel(Label label) { 229 int eventId = getEventId(); 230 touchPointListener.beforeLabel(eventId, label, currentLine, mv); 231 super.visitLabel(label); 232 touchPointListener.afterLabel(eventId, label, currentLine, mv); 233 } 234 235 @Override 236 public void visitJumpInsn(int opcode, Label label) { 237 /* Ignore any jump instructions in the "class init" method. 238 When initializing static variables, the JVM first checks 239 that the variable is null before attempting to set it. 240 This check contains an IFNONNULL jump instruction which 241 would confuse people if it showed up in the reports.*/ 242 if ((opcode != Opcodes.GOTO) 243 && (opcode != Opcodes.JSR) && (currentLine != 0) 244 && (!methodName.equals("<clinit>"))) { 245 int eventId = getEventId(); 246 touchPointListener.beforeJump(eventId, label, currentLine, mv); 247 super.visitJumpInsn(opcode, label); 248 touchPointListener.afterJump(eventId, label, currentLine, mv); 249 } else { 250 super.visitJumpInsn(opcode, label); 251 } 252 } 253 254 @Override 255 public void visitMethodInsn(int opcode, String owner, String method, 256 String descr) { 257 super.visitMethodInsn(opcode, owner, method, descr); 258 //We skip lines that contains call to methods that are specified inside ignoreRegexp 259 if (RegexUtil.matches(ignoreRegexp, owner)) { 260 touchPointListener.ignoreLine(getEventId(), currentLine); 261 } 262 } 263 264 @Override 265 public void visitLookupSwitchInsn(Label def, int[] values, Label[] labels) { 266 touchPointListener.beforeSwitch(getEventId(), def, labels, currentLine, mv, tryToFindSignatureOfConditionEnum()); 267 super.visitLookupSwitchInsn(def, values, labels); 268 } 269 270 @Override 271 public void visitTableSwitchInsn(int min, int max, Label def, Label[] labels) { 272 touchPointListener.beforeSwitch(getEventId(), def, labels, currentLine, mv, tryToFindSignatureOfConditionEnum()); 273 super.visitTableSwitchInsn(min, max, def, labels); 274 } 275 276 enum Abc {A, B}; 277 278/** 279 * We try to detect such a last 2 instructions and extract the enum signature. 280 * @code{ 281 INVOKESTATIC FindTouchPointsMethodAdapter.$SWITCH_TABLE$net$sourceforge$cobertura$instrument$FindTouchPointsMethodAdapter$Abc() : int[] 282 ALOAD 1: a 283 INVOKEVIRTUAL FindTouchPointsMethodAdapter$Abc.ordinal() : int 284 IALOAD 285 * } 286 */ 287 private String tryToFindSignatureOfConditionEnum() { 288// mv.visitMethodInsn(INVOKESTATIC, "net/sourceforge/cobertura/instrument/FindTouchPointsMethodAdapter", "$SWITCH_TABLE$net$sourceforge$cobertura$instrument$FindTouchPointsMethodAdapter$Abc", "()[I"); 289// mv.visitVarInsn(ALOAD, 1); 290// mv.visitMethodInsn(INVOKEVIRTUAL, "net/sourceforge/cobertura/instrument/FindTouchPointsMethodAdapter$Abc", "ordinal", "()I"); 291// mv.visitInsn(IALOAD); 292 293 if (backlog == null || backlog.size() < 4) return null; 294 int last = backlog.size() - 1; 295 if ((backlog.get(last) instanceof InsnNode) 296 && (backlog.get(last - 1) instanceof MethodInsnNode) 297 && (backlog.get(last - 2) instanceof VarInsnNode)) { 298 VarInsnNode i2 = (VarInsnNode)backlog.get(last - 2); 299 MethodInsnNode i3 = (MethodInsnNode)backlog.get(last - 1); 300 InsnNode i4 = (InsnNode)backlog.get(last); 301 if ((i2.getOpcode() == Opcodes.ALOAD) 302 && (i3.getOpcode() == Opcodes.INVOKEVIRTUAL && i3.name.equals("ordinal")) 303 && (i4.getOpcode() == Opcodes.IALOAD)) { 304 return i3.owner; 305 } 306 } 307 return null; 308 } 309 310 // =========== Getters and setters ===================== 311 312 /** 313 * Gets backing listener that will be informed about all interesting events found 314 */ 315 public TouchPointListener getTouchPointListener() { 316 return touchPointListener; 317 } 318 319 /**Niestety terminalnie. 320 * Sets backing listener that will be informed about all interesting events found 321 */ 322 public void setTouchPointListener(TouchPointListener touchPointListener) { 323 this.touchPointListener = touchPointListener; 324 } 325 326 /** 327 * @return list of patterns to know that we don't want trace lines that are calls to some methods 328 */ 329 public Collection<Pattern> getIgnoreRegexp() { 330 return ignoreRegexp; 331 } 332 333 /** 334 * sets list of patterns to know that we don't want trace lines that are calls to some methods 335 */ 336 public void setIgnoreRegexp(Collection<Pattern> ignoreRegexp) { 337 this.ignoreRegexp = ignoreRegexp; 338 } 339 340}