| /******************************************************************************* |
| * 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.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; |
| |
| /** |
| * <code>true</code> if method is a class or interface initialization |
| * method. |
| */ |
| private final boolean clinit; |
| |
| /** 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 name |
| * the method's name |
| * @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 name, final String desc, final MethodVisitor mv, |
| final IProbeArrayStrategy arrayStrategy) { |
| super(InstrSupport.ASM_API_VERSION, mv); |
| this.clinit = InstrSupport.CLINIT_NAME.equals(name); |
| 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) { |
| |
| // BEGIN android-change |
| // For a probe we call setProbe on the IExecutionData object. |
| |
| mv.visitVarInsn(Opcodes.ALOAD, variable); |
| |
| // Stack[0]: Lorg/jacoco/core/data/IExecutionData; |
| |
| InstrSupport.push(mv, id); |
| |
| // Stack[1]: I |
| // Stack[0]: Lorg/jacoco/core/data/IExecutionData; |
| |
| mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "org/jacoco/core/data/IExecutionData", "setProbe", "(I)V", true); |
| // END android-change |
| } |
| |
| @Override |
| public void visitCode() { |
| accessorStackSize = arrayStrategy.storeInstance(mv, clinit, 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); |
| } |
| |
| } |