| /******************************************************************************* |
| * Copyright (c) 2009, 2019 Mountainminds GmbH & Co. KG and Contributors |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Marc R. Hoffmann - initial API and implementation |
| * |
| *******************************************************************************/ |
| package org.jacoco.core.runtime; |
| |
| import java.util.logging.Handler; |
| import java.util.logging.Level; |
| import java.util.logging.LogRecord; |
| import java.util.logging.Logger; |
| |
| import org.jacoco.core.internal.instr.InstrSupport; |
| import org.objectweb.asm.MethodVisitor; |
| import org.objectweb.asm.Opcodes; |
| |
| /** |
| * This {@link IRuntime} implementation uses the Java logging API to report |
| * coverage data. |
| * <p> |
| * |
| * The implementation uses a dedicated log channel. Instrumented classes call |
| * {@link Logger#log(Level, String, Object[])} with the class identifier in the |
| * first slot of the parameter array. The runtime implements a {@link Handler} |
| * for this channel that puts the probe data structure into the first slot of |
| * the parameter array. |
| */ |
| public class LoggerRuntime extends AbstractRuntime { |
| |
| private static final String CHANNEL = "jacoco-runtime"; |
| |
| private final String key; |
| |
| private final Logger logger; |
| |
| private final Handler handler; |
| |
| /** |
| * Creates a new runtime. |
| */ |
| public LoggerRuntime() { |
| super(); |
| this.key = Integer.toHexString(hashCode()); |
| this.logger = configureLogger(); |
| this.handler = new RuntimeHandler(); |
| } |
| |
| private Logger configureLogger() { |
| final Logger l = Logger.getLogger(CHANNEL); |
| l.setUseParentHandlers(false); |
| l.setLevel(Level.ALL); |
| return l; |
| } |
| |
| public int generateDataAccessor(final long classid, final String classname, |
| final int probecount, final MethodVisitor mv) { |
| |
| // The data accessor performs the following steps: |
| // |
| // final Object[] args = new Object[3]; |
| // args[0] = Long.valueOf(classid); |
| // args[1] = classname; |
| // args[2] = Integer.valueOf(probecount); |
| // Logger.getLogger(CHANNEL).log(Level.INFO, key, args); |
| // final byte[] probedata = (byte[]) args[0]; |
| // |
| // Note that local variable 'args' is used at two places. As were not |
| // allowed to allocate local variables we have to keep this value with |
| // DUP and SWAP operations on the operand stack. |
| |
| // 1. Create parameter array: |
| |
| RuntimeData.generateArgumentArray(classid, classname, probecount, mv); |
| |
| // Stack[0]: [Ljava/lang/Object; |
| |
| mv.visitInsn(Opcodes.DUP); |
| |
| // Stack[1]: [Ljava/lang/Object; |
| // Stack[0]: [Ljava/lang/Object; |
| |
| // 2. Call Logger: |
| |
| mv.visitLdcInsn(CHANNEL); |
| mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/logging/Logger", |
| "getLogger", "(Ljava/lang/String;)Ljava/util/logging/Logger;", |
| false); |
| |
| // Stack[2]: Ljava/util/logging/Logger; |
| // Stack[1]: [Ljava/lang/Object; |
| // Stack[0]: [Ljava/lang/Object; |
| |
| mv.visitInsn(Opcodes.SWAP); |
| |
| // Stack[2]: [Ljava/lang/Object; |
| // Stack[1]: Ljava/util/logging/Logger; |
| // Stack[0]: [Ljava/lang/Object; |
| |
| mv.visitFieldInsn(Opcodes.GETSTATIC, "java/util/logging/Level", "INFO", |
| "Ljava/util/logging/Level;"); |
| |
| // Stack[3]: Ljava/util/logging/Level; |
| // Stack[2]: [Ljava/lang/Object; |
| // Stack[1]: Ljava/util/logging/Logger; |
| // Stack[0]: [Ljava/lang/Object; |
| |
| mv.visitInsn(Opcodes.SWAP); |
| |
| // Stack[3]: [Ljava/lang/Object; |
| // Stack[2]: Ljava/util/logging/Level; |
| // Stack[1]: Ljava/util/logging/Logger; |
| // Stack[0]: [Ljava/lang/Object; |
| |
| mv.visitLdcInsn(key); |
| |
| // Stack[4]: Ljava/lang/String; |
| // Stack[3]: [Ljava/lang/Object; |
| // Stack[2]: Ljava/util/logging/Level; |
| // Stack[1]: Ljava/util/logging/Logger; |
| // Stack[0]: [Ljava/lang/Object; |
| |
| mv.visitInsn(Opcodes.SWAP); |
| |
| // Stack[4]: [Ljava/lang/Object; |
| // Stack[3]: Ljava/lang/String; |
| // Stack[2]: Ljava/util/logging/Level; |
| // Stack[1]: Ljava/util/logging/Logger; |
| // Stack[0]: [Ljava/lang/Object; |
| |
| mv.visitMethodInsn( |
| Opcodes.INVOKEVIRTUAL, |
| "java/util/logging/Logger", |
| "log", |
| "(Ljava/util/logging/Level;Ljava/lang/String;[Ljava/lang/Object;)V", |
| false); |
| |
| // Stack[0]: [Ljava/lang/Object; |
| |
| // 3. Load data structure from parameter array: |
| |
| mv.visitInsn(Opcodes.ICONST_0); |
| mv.visitInsn(Opcodes.AALOAD); |
| mv.visitTypeInsn(Opcodes.CHECKCAST, InstrSupport.DATAFIELD_DESC); |
| |
| // Stack[0]: [Z |
| |
| return 5; // Maximum local stack size is 5 |
| } |
| |
| @Override |
| public void startup(final RuntimeData data) throws Exception { |
| super.startup(data); |
| this.logger.addHandler(handler); |
| } |
| |
| public void shutdown() { |
| this.logger.removeHandler(handler); |
| } |
| |
| private class RuntimeHandler extends Handler { |
| |
| @Override |
| public void publish(final LogRecord record) { |
| if (key.equals(record.getMessage())) { |
| // BEGIN android-change |
| data.getExecutionData(record.getParameters()); |
| // END android-change |
| } |
| } |
| |
| @Override |
| public void flush() { |
| // nothing to do |
| } |
| |
| @Override |
| public void close() throws SecurityException { |
| // The Java logging framework removes and closes all handlers on JVM |
| // shutdown. As soon as our handler has been removed, all classes |
| // that might get instrumented during shutdown (e.g. loaded by other |
| // shutdown hooks) will fail to initialize. Therefore we add ourself |
| // again here. This is a nasty hack that might fail in some Java |
| // implementations. |
| logger.addHandler(handler); |
| } |
| } |
| |
| } |