LoggerRuntime.java revision f9843446d68ccdbab7fb4006b381b1f8abfbf091
1/******************************************************************************* 2 * Copyright (c) 2009, 2010 Mountainminds GmbH & Co. KG and Contributors 3 * All rights reserved. This program and the accompanying materials 4 * are made available under the terms of the Eclipse Public License v1.0 5 * which accompanies this distribution, and is available at 6 * http://www.eclipse.org/legal/epl-v10.html 7 * 8 * Contributors: 9 * Marc R. Hoffmann - initial API and implementation 10 * 11 * $Id: $ 12 *******************************************************************************/ 13package org.jacoco.core.runtime; 14 15import java.util.logging.Handler; 16import java.util.logging.Level; 17import java.util.logging.LogRecord; 18import java.util.logging.Logger; 19 20import org.jacoco.core.instr.GeneratorConstants; 21import org.objectweb.asm.MethodVisitor; 22import org.objectweb.asm.Opcodes; 23 24/** 25 * This {@link IRuntime} implementation uses the Java logging API to report 26 * coverage data. The advantage is, that the instrumented classes do not get 27 * dependencies to other classes than the JRE library itself. 28 * <p> 29 * 30 * The implementation uses a dedicated log channel. Instrumented classes call 31 * {@link Logger#log(Level, String, Object[])} with the class identifier in the 32 * first slot of the parameter array. The runtime implements a {@link Handler} 33 * for this channel that puts the probe data structure into the first slot of 34 * the parameter array. 35 * 36 * @author Marc R. Hoffmann 37 * @version $Revision: $ 38 */ 39public class LoggerRuntime extends AbstractRuntime { 40 41 private static final String CHANNEL = "jacoco-runtime"; 42 43 private final String key; 44 45 private final Logger logger; 46 47 private final Handler handler; 48 49 /** 50 * Creates a new runtime. 51 */ 52 public LoggerRuntime() { 53 this.key = Integer.toHexString(hashCode()); 54 this.logger = configureLogger(); 55 this.handler = new RuntimeHandler(); 56 } 57 58 private Logger configureLogger() { 59 final Logger l = Logger.getLogger(CHANNEL); 60 l.setUseParentHandlers(false); 61 l.setLevel(Level.ALL); 62 return l; 63 } 64 65 public int generateDataAccessor(final long classid, final MethodVisitor mv) { 66 67 // The data accessor performs the following steps: 68 // 69 // final Object[] args = new Object[1]; 70 // args[0] = Long.valueOf(classid); 71 // Logger.getLogger(CHANNEL).log(Level.INFO, key, args); 72 // final byte[] probedata = (byte[]) args[0]; 73 // 74 // Note that local variable 'args' is used at two places. As were not 75 // allowed to allocate local variables we have to keep this value with 76 // DUP and SWAP operations on the operand stack. 77 78 // 1. Create parameter array: 79 80 mv.visitInsn(Opcodes.ICONST_1); 81 mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); 82 83 // Stack[0]: [Ljava/lang/Object; 84 85 mv.visitInsn(Opcodes.DUP); 86 87 // Stack[1]: [Ljava/lang/Object; 88 // Stack[0]: [Ljava/lang/Object; 89 90 mv.visitInsn(Opcodes.ICONST_0); 91 mv.visitLdcInsn(Long.valueOf(classid)); 92 93 // Stack[4]: J 94 // Stack[3]: . 95 // Stack[2]: I 96 // Stack[1]: [Ljava/lang/Object; 97 // Stack[0]: [Ljava/lang/Object; 98 99 mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf", 100 "(J)Ljava/lang/Long;"); 101 102 // Stack[3]: Ljava/lang/Long; 103 // Stack[2]: I 104 // Stack[1]: [Ljava/lang/Object; 105 // Stack[0]: [Ljava/lang/Object; 106 107 mv.visitInsn(Opcodes.AASTORE); 108 109 // Stack[0]: [Ljava/lang/Object; 110 111 mv.visitInsn(Opcodes.DUP); 112 113 // Stack[1]: [Ljava/lang/Object; 114 // Stack[0]: [Ljava/lang/Object; 115 116 // 2. Call Logger: 117 118 mv.visitLdcInsn(CHANNEL); 119 mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/logging/Logger", 120 "getLogger", "(Ljava/lang/String;)Ljava/util/logging/Logger;"); 121 122 // Stack[2]: Ljava/util/logging/Logger; 123 // Stack[1]: [Ljava/lang/Object; 124 // Stack[0]: [Ljava/lang/Object; 125 126 mv.visitInsn(Opcodes.SWAP); 127 128 // Stack[2]: [Ljava/lang/Object; 129 // Stack[1]: Ljava/util/logging/Logger; 130 // Stack[0]: [Ljava/lang/Object; 131 132 mv.visitFieldInsn(Opcodes.GETSTATIC, "java/util/logging/Level", "INFO", 133 "Ljava/util/logging/Level;"); 134 135 // Stack[3]: Ljava/util/logging/Level; 136 // Stack[2]: [Ljava/lang/Object; 137 // Stack[1]: Ljava/util/logging/Logger; 138 // Stack[0]: [Ljava/lang/Object; 139 140 mv.visitInsn(Opcodes.SWAP); 141 142 // Stack[3]: [Ljava/lang/Object; 143 // Stack[2]: Ljava/util/logging/Level; 144 // Stack[1]: Ljava/util/logging/Logger; 145 // Stack[0]: [Ljava/lang/Object; 146 147 mv.visitLdcInsn(key); 148 149 // Stack[4]: Ljava/lang/String; 150 // Stack[3]: [Ljava/lang/Object; 151 // Stack[2]: Ljava/util/logging/Level; 152 // Stack[1]: Ljava/util/logging/Logger; 153 // Stack[0]: [Ljava/lang/Object; 154 155 mv.visitInsn(Opcodes.SWAP); 156 157 // Stack[4]: [Ljava/lang/Object; 158 // Stack[3]: Ljava/lang/String; 159 // Stack[2]: Ljava/util/logging/Level; 160 // Stack[1]: Ljava/util/logging/Logger; 161 // Stack[0]: [Ljava/lang/Object; 162 163 mv 164 .visitMethodInsn(Opcodes.INVOKEVIRTUAL, 165 "java/util/logging/Logger", "log", 166 "(Ljava/util/logging/Level;Ljava/lang/String;[Ljava/lang/Object;)V"); 167 168 // Stack[0]: [Ljava/lang/Object; 169 170 // 3. Load data structure from parameter array: 171 172 mv.visitInsn(Opcodes.ICONST_0); 173 174 // Stack[1]: I 175 // Stack[0]: [Ljava/lang/Object; 176 177 mv.visitInsn(Opcodes.AALOAD); 178 mv.visitTypeInsn(Opcodes.CHECKCAST, GeneratorConstants.PROBEDATA_TYPE 179 .getInternalName()); 180 181 // Stack[0]: [Z 182 183 return 5; // Maximum local stack size is 5 184 } 185 186 public void startup() { 187 this.logger.addHandler(handler); 188 } 189 190 public void shutdown() { 191 this.logger.removeHandler(handler); 192 } 193 194 private class RuntimeHandler extends Handler { 195 196 @Override 197 public void publish(final LogRecord record) { 198 if (key.equals(record.getMessage())) { 199 final Object[] params = record.getParameters(); 200 final Long id = (Long) params[0]; 201 synchronized (store) { 202 final boolean[] data = store.getData(id); 203 if (data == null) { 204 throw new IllegalStateException(String.format( 205 "Unknown class id %x.", id)); 206 } 207 params[0] = data; 208 } 209 } 210 } 211 212 @Override 213 public void flush() { 214 } 215 216 @Override 217 public void close() throws SecurityException { 218 // The Java logging framework removes and closes all handlers on JVM 219 // shutdown. As soon as our handler has been removed, all classes 220 // that might get instrumented during shutdown (e.g. loaded by other 221 // shutdown hooks) will fail to initialize. Therefore we add ourself 222 // again here. 223 // This is a nasty hack that might fail in some Java 224 // implementations. 225 logger.addHandler(handler); 226 } 227 } 228 229} 230