# ProbeInserter.java

    /*******************************************************************************
     * Copyright (c) 2009, 2013 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 org.objectweb.asm.Label;
    import org.objectweb.asm.MethodVisitor;
    import org.objectweb.asm.Opcodes;
    import org.objectweb.asm.Type;

    /**
     * Internal utility to add probes into the control flow of a method. The code
     * for a probe simply sets a certain slot of a boolean array to true. In
     * addition the probe array has to be retrieved at the beginning of the method
     * and stored in a local variable.
     */
    class ProbeInserter extends MethodVisitor implements IProbeInserter {

    	private final IProbeArrayStrategy arrayStrategy;

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

    	/** Maximum stack usage of the code to access the probe array. */
    	private int accessorStackSize;

    	/**
    	 * Creates a new {@link ProbeInserter}.
    	 *
    	 * @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
    	 * @param arrayStrategy
    	 *            callback to create the code that retrieves the reference to
    	 *            the probe array
    	 */
    	ProbeInserter(final int access, final String desc, final MethodVisitor mv,
    			final IProbeArrayStrategy arrayStrategy) {
    		super(Opcodes.ASM4, mv);
    		this.arrayStrategy = arrayStrategy;
    		int pos = (Opcodes.ACC_STATIC & access) == 0 ? 1 : 0;
    		for (final Type t : Type.getArgumentTypes(desc)) {
    			pos += t.getSize();
    		}
    		variable = pos;
    	}

    	public void insertProbe(final int id) {

    		// For a probe we set the corresponding position in the boolean[] array
    		// to true.

    		mv.visitVarInsn(Opcodes.ALOAD, variable);

    		// Stack[0]: [Z

    		InstrSupport.push(mv, id);

    		// Stack[1]: I
    		// Stack[0]: [Z

    		mv.visitInsn(Opcodes.ICONST_1);

    		// Stack[2]: I
    		// Stack[1]: I
    		// Stack[0]: [Z

    		mv.visitInsn(Opcodes.BASTORE);
    	}

    	@Override
    	public void visitCode() {
    		accessorStackSize = arrayStrategy.storeInstance(mv, variable);
    		mv.visitCode();
    	}

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

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

    	@Override
    	public final 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));
    	}

    	@Override
    	public void visitMaxs(final int maxStack, final int maxLocals) {
    		// Max stack size of the probe code is 3 which can add to the
    		// original stack size depending on the probe locations. The accessor
    		// stack size is an absolute maximum, as the accessor code is inserted
    		// at the very beginning of each method when the stack size is empty.
    		final int increasedStack = Math.max(maxStack + 3, accessorStackSize);
    		mv.visitMaxs(increasedStack, maxLocals + 1);
    	}

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

    	@Override
    	public final 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 IllegalArgumentException(
    					"ClassReader.accept() should be called with EXPAND_FRAMES flag");
    		}

    		final Object[] newLocal = new Object[Math.max(nLocal, variable) + 1];
    		int idx = 0; // Arrays index for existing locals
    		int newIdx = 0; // Array index for new locals
    		int pos = 0; // Current variable position
    		while (idx < nLocal || pos <= variable) {
    			if (pos == variable) {
    				newLocal[newIdx++] = InstrSupport.DATAFIELD_DESC;
    				pos++;
    			} else {
    				if (idx < nLocal) {
    					final Object t = local[idx++];
    					newLocal[newIdx++] = t;
    					pos++;
    					if (t == Opcodes.LONG || t == Opcodes.DOUBLE) {
    						pos++;
    					}
    				} else {
    					// Fill unused slots with TOP
    					newLocal[newIdx++] = Opcodes.TOP;
    					pos++;
    				}
    			}
    		}
    		mv.visitFrame(type, newIdx, newLocal, nStack, stack);
    	}

    }