LoggerRuntime.java
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 String classname,
66 final int probecount, final MethodVisitor mv) {
67
68 // The data accessor performs the following steps:
69 //
70 // final Object[] args = new Object[3];
71 // args[0] = Long.valueOf(classid);
72 // args[1] = classname;
73 // args[2] = Integer.valueOf(probecount);
74 // Logger.getLogger(CHANNEL).log(Level.INFO, key, args);
75 // final byte[] probedata = (byte[]) args[0];
76 //
77 // Note that local variable 'args' is used at two places. As were not
78 // allowed to allocate local variables we have to keep this value with
79 // DUP and SWAP operations on the operand stack.
80
81 // 1. Create parameter array:
82
83 ExecutionDataAccess.generateArgumentArray(classid, classname, probecount, mv);
84
85 // Stack[0]: [Ljava/lang/Object;
86
87 mv.visitInsn(Opcodes.DUP);
88
89 // Stack[1]: [Ljava/lang/Object;
90 // Stack[0]: [Ljava/lang/Object;
91
92 // 2. Call Logger:
93
94 mv.visitLdcInsn(CHANNEL);
95 mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/logging/Logger",
96 "getLogger", "(Ljava/lang/String;)Ljava/util/logging/Logger;");
97
98 // Stack[2]: Ljava/util/logging/Logger;
99 // Stack[1]: [Ljava/lang/Object;
100 // Stack[0]: [Ljava/lang/Object;
101
102 mv.visitInsn(Opcodes.SWAP);
103
104 // Stack[2]: [Ljava/lang/Object;
105 // Stack[1]: Ljava/util/logging/Logger;
106 // Stack[0]: [Ljava/lang/Object;
107
108 mv.visitFieldInsn(Opcodes.GETSTATIC, "java/util/logging/Level", "INFO",
109 "Ljava/util/logging/Level;");
110
111 // Stack[3]: Ljava/util/logging/Level;
112 // Stack[2]: [Ljava/lang/Object;
113 // Stack[1]: Ljava/util/logging/Logger;
114 // Stack[0]: [Ljava/lang/Object;
115
116 mv.visitInsn(Opcodes.SWAP);
117
118 // Stack[3]: [Ljava/lang/Object;
119 // Stack[2]: Ljava/util/logging/Level;
120 // Stack[1]: Ljava/util/logging/Logger;
121 // Stack[0]: [Ljava/lang/Object;
122
123 mv.visitLdcInsn(key);
124
125 // Stack[4]: Ljava/lang/String;
126 // Stack[3]: [Ljava/lang/Object;
127 // Stack[2]: Ljava/util/logging/Level;
128 // Stack[1]: Ljava/util/logging/Logger;
129 // Stack[0]: [Ljava/lang/Object;
130
131 mv.visitInsn(Opcodes.SWAP);
132
133 // Stack[4]: [Ljava/lang/Object;
134 // Stack[3]: Ljava/lang/String;
135 // Stack[2]: Ljava/util/logging/Level;
136 // Stack[1]: Ljava/util/logging/Logger;
137 // Stack[0]: [Ljava/lang/Object;
138
139 mv
140 .visitMethodInsn(Opcodes.INVOKEVIRTUAL,
141 "java/util/logging/Logger", "log",
142 "(Ljava/util/logging/Level;Ljava/lang/String;[Ljava/lang/Object;)V");
143
144 // Stack[0]: [Ljava/lang/Object;
145
146 // 3. Load data structure from parameter array:
147
148 mv.visitInsn(Opcodes.ICONST_0);
149
150 // Stack[1]: I
151 // Stack[0]: [Ljava/lang/Object;
152
153 mv.visitInsn(Opcodes.AALOAD);
154 mv.visitTypeInsn(Opcodes.CHECKCAST, GeneratorConstants.PROBEDATA_TYPE
155 .getInternalName());
156
157 // Stack[0]: [Z
158
159 return 5; // Maximum local stack size is 5
160 }
161
162 public void startup() {
163 this.logger.addHandler(handler);
164 }
165
166 public void shutdown() {
167 this.logger.removeHandler(handler);
168 }
169
170 private class RuntimeHandler extends Handler {
171
172 @Override
173 public void publish(final LogRecord record) {
174 if (key.equals(record.getMessage())) {
175 access.getExecutionData(record.getParameters());
176 }
177 }
178
179 @Override
180 public void flush() {
181 }
182
183 @Override
184 public void close() throws SecurityException {
185 // The Java logging framework removes and closes all handlers on JVM
186 // shutdown. As soon as our handler has been removed, all classes
187 // that might get instrumented during shutdown (e.g. loaded by other
188 // shutdown hooks) will fail to initialize. Therefore we add ourself
189 // again here.
190 // This is a nasty hack that might fail in some Java
191 // implementations.
192 logger.addHandler(handler);
193 }
194 }
195
196}