1/******************************************************************************* 2 * Copyright (c) 2009, 2015 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 *******************************************************************************/ 12package org.jacoco.core.runtime; 13 14import java.util.logging.Handler; 15import java.util.logging.Level; 16import java.util.logging.LogRecord; 17import java.util.logging.Logger; 18 19import org.jacoco.core.internal.instr.InstrSupport; 20import org.objectweb.asm.MethodVisitor; 21import org.objectweb.asm.Opcodes; 22 23/** 24 * This {@link IRuntime} implementation uses the Java logging API to report 25 * coverage data. 26 * <p> 27 * 28 * The implementation uses a dedicated log channel. Instrumented classes call 29 * {@link Logger#log(Level, String, Object[])} with the class identifier in the 30 * first slot of the parameter array. The runtime implements a {@link Handler} 31 * for this channel that puts the probe data structure into the first slot of 32 * the parameter array. 33 */ 34public class LoggerRuntime extends AbstractRuntime { 35 36 private static final String CHANNEL = "jacoco-runtime"; 37 38 private final String key; 39 40 private final Logger logger; 41 42 private final Handler handler; 43 44 /** 45 * Creates a new runtime. 46 */ 47 public LoggerRuntime() { 48 super(); 49 this.key = Integer.toHexString(hashCode()); 50 this.logger = configureLogger(); 51 this.handler = new RuntimeHandler(); 52 } 53 54 private Logger configureLogger() { 55 final Logger l = Logger.getLogger(CHANNEL); 56 l.setUseParentHandlers(false); 57 l.setLevel(Level.ALL); 58 return l; 59 } 60 61 public int generateDataAccessor(final long classid, final String classname, 62 final int probecount, final MethodVisitor mv) { 63 64 // The data accessor performs the following steps: 65 // 66 // final Object[] args = new Object[3]; 67 // args[0] = Long.valueOf(classid); 68 // args[1] = classname; 69 // args[2] = Integer.valueOf(probecount); 70 // Logger.getLogger(CHANNEL).log(Level.INFO, key, args); 71 // final byte[] probedata = (byte[]) args[0]; 72 // 73 // Note that local variable 'args' is used at two places. As were not 74 // allowed to allocate local variables we have to keep this value with 75 // DUP and SWAP operations on the operand stack. 76 77 // 1. Create parameter array: 78 79 RuntimeData.generateArgumentArray(classid, classname, probecount, mv); 80 81 // Stack[0]: [Ljava/lang/Object; 82 83 mv.visitInsn(Opcodes.DUP); 84 85 // Stack[1]: [Ljava/lang/Object; 86 // Stack[0]: [Ljava/lang/Object; 87 88 // 2. Call Logger: 89 90 mv.visitLdcInsn(CHANNEL); 91 mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/logging/Logger", 92 "getLogger", "(Ljava/lang/String;)Ljava/util/logging/Logger;", 93 false); 94 95 // Stack[2]: Ljava/util/logging/Logger; 96 // Stack[1]: [Ljava/lang/Object; 97 // Stack[0]: [Ljava/lang/Object; 98 99 mv.visitInsn(Opcodes.SWAP); 100 101 // Stack[2]: [Ljava/lang/Object; 102 // Stack[1]: Ljava/util/logging/Logger; 103 // Stack[0]: [Ljava/lang/Object; 104 105 mv.visitFieldInsn(Opcodes.GETSTATIC, "java/util/logging/Level", "INFO", 106 "Ljava/util/logging/Level;"); 107 108 // Stack[3]: Ljava/util/logging/Level; 109 // Stack[2]: [Ljava/lang/Object; 110 // Stack[1]: Ljava/util/logging/Logger; 111 // Stack[0]: [Ljava/lang/Object; 112 113 mv.visitInsn(Opcodes.SWAP); 114 115 // Stack[3]: [Ljava/lang/Object; 116 // Stack[2]: Ljava/util/logging/Level; 117 // Stack[1]: Ljava/util/logging/Logger; 118 // Stack[0]: [Ljava/lang/Object; 119 120 mv.visitLdcInsn(key); 121 122 // Stack[4]: Ljava/lang/String; 123 // Stack[3]: [Ljava/lang/Object; 124 // Stack[2]: Ljava/util/logging/Level; 125 // Stack[1]: Ljava/util/logging/Logger; 126 // Stack[0]: [Ljava/lang/Object; 127 128 mv.visitInsn(Opcodes.SWAP); 129 130 // Stack[4]: [Ljava/lang/Object; 131 // Stack[3]: Ljava/lang/String; 132 // Stack[2]: Ljava/util/logging/Level; 133 // Stack[1]: Ljava/util/logging/Logger; 134 // Stack[0]: [Ljava/lang/Object; 135 136 mv.visitMethodInsn( 137 Opcodes.INVOKEVIRTUAL, 138 "java/util/logging/Logger", 139 "log", 140 "(Ljava/util/logging/Level;Ljava/lang/String;[Ljava/lang/Object;)V", 141 false); 142 143 // Stack[0]: [Ljava/lang/Object; 144 145 // 3. Load data structure from parameter array: 146 147 mv.visitInsn(Opcodes.ICONST_0); 148 mv.visitInsn(Opcodes.AALOAD); 149 mv.visitTypeInsn(Opcodes.CHECKCAST, InstrSupport.DATAFIELD_DESC); 150 151 // Stack[0]: [Z 152 153 return 5; // Maximum local stack size is 5 154 } 155 156 @Override 157 public void startup(final RuntimeData data) throws Exception { 158 super.startup(data); 159 this.logger.addHandler(handler); 160 } 161 162 public void shutdown() { 163 this.logger.removeHandler(handler); 164 } 165 166 private class RuntimeHandler extends Handler { 167 168 @Override 169 public void publish(final LogRecord record) { 170 if (key.equals(record.getMessage())) { 171 data.getProbes(record.getParameters()); 172 } 173 } 174 175 @Override 176 public void flush() { 177 // nothing to do 178 } 179 180 @Override 181 public void close() throws SecurityException { 182 // The Java logging framework removes and closes all handlers on JVM 183 // shutdown. As soon as our handler has been removed, all classes 184 // that might get instrumented during shutdown (e.g. loaded by other 185 // shutdown hooks) will fail to initialize. Therefore we add ourself 186 // again here. This is a nasty hack that might fail in some Java 187 // implementations. 188 logger.addHandler(handler); 189 } 190 } 191 192} 193