ProbeVariableInserter.java

/*******************************************************************************
 * Copyright (c) 2009, 2012 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.internal.instr;

import java.util.HashMap;
import java.util.Map;

import org.jacoco.core.internal.flow.LabelInfo;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.FrameNode;

/**
 * Internal utility to add a local variable for the probe array at the beginning
 * of every method. The adapter adjusts all other local variables and frames
 * accordingly. In addition the adapter records the frames at certain labels to
 * re-insert them if required for jump probes.
 * 
 * Basically this is a simplified version of ASM's
 * {@link org.objectweb.asm.commons.LocalVariablesSorter} to avoid expensive
 * mapping tables and prevents ASM bug #314563.
 */
class ProbeVariableInserter extends MethodAdapter {

	/** Position of the inserted variable. */
	protected final int variable;

	/** Index the inserted variable. */
	private final int variableIdx;

	private boolean firstFrame = true;

	private Label lastLabel;

	private Map<Label, FrameNode> probeFrames;

	/**
	 * Creates a new {@link ProbeVariableInserter}.
	 * 
	 * @param access
	 *            access flags of the adapted method.
	 * @param desc
	 *            the method's descriptor
	 * @param mv
	 *            the method visitor to which this adapter delegates calls
	 */
	ProbeVariableInserter(final int access, final String desc,
			final MethodVisitor mv) {
		super(mv);
		int idx = (Opcodes.ACC_STATIC & access) == 0 ? 1 : 0;
		int pos = idx;
		for (final Type t : Type.getArgumentTypes(desc)) {
			idx++;
			pos += t.getSize();
		}
		variableIdx = idx;
		variable = pos;
		lastLabel = null;
	}

	@Override
	public void visitLabel(final Label label) {
		mv.visitLabel(label);
		lastLabel = label;
	}

	@Override
	public void visitVarInsn(final int opcode, final int var) {
		mv.visitVarInsn(opcode, map(var));
	}

	@Override
	public void visitIincInsn(final int var, final int increment) {
		mv.visitIincInsn(map(var), increment);
	}

	@Override
	public void visitMaxs(final int maxStack, final int maxLocals) {
		mv.visitMaxs(maxStack, maxLocals + 1);
	}

	@Override
	public void visitLocalVariable(final String name, final String desc,
			final String signature, final Label start, final Label end,
			final int index) {
		mv.visitLocalVariable(name, desc, signature, start, end, map(index));
	}

	private int map(final int var) {
		if (var < variable) {
			return var;
		} else {
			return var + 1;
		}
	}

	@Override
	public void visitFrame(final int type, final int nLocal,
			final Object[] local, final int nStack, final Object[] stack) {

		if (type != Opcodes.F_NEW) { // uncompressed frame
			throw new IllegalStateException(
					"ClassReader.accept() should be called with EXPAND_FRAMES flag");
		}

		final Object[] newLocal = new Object[nLocal + 1];
		for (int i = 0; i <= local.length; i++) {
			if (i < variableIdx) {
				newLocal[i] = local[i];
				continue;
			}
			if (i > variableIdx) {
				newLocal[i] = local[i - 1];
				continue;
			}
			newLocal[i] = InstrSupport.DATAFIELD_DESC;
		}

		if (lastLabel != null) {
			if (LabelInfo.isMultiTarget(lastLabel)) {
				// Create map instance only if required:
				if (probeFrames == null) {
					probeFrames = new HashMap<Label, FrameNode>();
				}
				probeFrames.put(lastLabel, new FrameNode(type, nLocal + 1,
						newLocal, nStack, stack));
			}
			lastLabel = null;
		}

		if (firstFrame) {
			// The first frame is generated by ASM and represents the implicit
			// frame derived from the method signature only. This frame must not
			// yet be modified.
			mv.visitFrame(type, nLocal, local, nStack, stack);
			firstFrame = false;
		} else {
			mv.visitFrame(type, nLocal + 1, newLocal, nStack, stack);
		}
	}

	/**
	 * Inserts the frame again that was inserted after the given label.
	 * 
	 * @param label
	 *            label of the frame to insert
	 */
	protected void insertProbeFrame(final Label label) {
		if (probeFrames != null) {
			final FrameNode frame = probeFrames.get(label);
			if (frame != null) {
				frame.accept(mv);
			}
		}
	}

}