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