ClassInstrumenter.java
1/*******************************************************************************
2 * Copyright (c) 2009 Mountainminds GmbH & Co. KG and others
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.instr;
14
15import static java.lang.String.format;
16
17import java.util.ArrayList;
18import java.util.List;
19
20import org.jacoco.core.runtime.IRuntime;
21import org.objectweb.asm.ClassAdapter;
22import org.objectweb.asm.ClassVisitor;
23import org.objectweb.asm.FieldVisitor;
24import org.objectweb.asm.Label;
25import org.objectweb.asm.MethodVisitor;
26import org.objectweb.asm.Opcodes;
27import org.objectweb.asm.Type;
28import org.objectweb.asm.commons.EmptyVisitor;
29import org.objectweb.asm.commons.GeneratorAdapter;
30
31/**
32 * Adapter that instruments a class for coverage tracing.
33 *
34 * @author Marc R. Hoffmann
35 * @version $Revision: $
36 */
37public class ClassInstrumenter extends ClassAdapter {
38
39 private static class EmptyBlockMethodVisitor extends EmptyVisitor implements
40 IBlockMethodVisitor {
41
42 public void visitBlockEndBeforeJump(final int id) {
43 }
44
45 public void visitBlockEnd(final int id) {
46 }
47
48 }
49
50 private final long id;
51
52 private final IRuntime runtime;
53
54 private final List<BlockMethodAdapter> blockCounters;
55
56 private Type type;
57
58 /**
59 * Emits a instrumented version of this class to the given class visitor
60 *
61 * @param id
62 * unique identifier given to this class
63 * @param runtime
64 * this runtime will be used for instrumentation
65 * @param cv
66 * next delegate in the visitor chain will receive the
67 * instrumented class
68 */
69 public ClassInstrumenter(final long id, final IRuntime runtime,
70 final ClassVisitor cv) {
71 super(cv);
72 this.id = id;
73 this.runtime = runtime;
74 this.blockCounters = new ArrayList<BlockMethodAdapter>();
75 }
76
77 @Override
78 public void visit(final int version, final int access, final String name,
79 final String signature, final String superName,
80 final String[] interfaces) {
81 this.type = Type.getObjectType(name);
82 super.visit(version, access, name, signature, superName, interfaces);
83 }
84
85 @Override
86 public FieldVisitor visitField(final int access, final String name,
87 final String desc, final String signature, final Object value) {
88 assertNotInstrumented(name, GeneratorConstants.DATAFIELD_NAME);
89 return super.visitField(access, name, desc, signature, value);
90 }
91
92 @Override
93 public MethodVisitor visitMethod(final int access, final String name,
94 final String desc, final String signature, final String[] exceptions) {
95
96 assertNotInstrumented(name, GeneratorConstants.INIT_METHOD.getName());
97
98 final MethodVisitor mv = super.visitMethod(access, name, desc,
99 signature, exceptions);
100
101 // Abstract methods do not have code to analyze
102 if ((access & Opcodes.ACC_ABSTRACT) != 0) {
103 return mv;
104 }
105
106 final int methodId = blockCounters.size();
107
108 final IBlockMethodVisitor blockVisitor;
109 if (mv == null) {
110 blockVisitor = new EmptyBlockMethodVisitor();
111 } else {
112 blockVisitor = new MethodInstrumenter(mv, access, name, desc,
113 methodId, type);
114 }
115
116 final BlockMethodAdapter adapter = new BlockMethodAdapter(blockVisitor,
117 access, name, desc, signature, exceptions);
118 blockCounters.add(adapter);
119 return adapter;
120 }
121
122 @Override
123 public void visitEnd() {
124 createDataField();
125 createInitMethod();
126 registerClass();
127 super.visitEnd();
128 }
129
130 private void createDataField() {
131 super.visitField(GeneratorConstants.DATAFIELD_ACC,
132 GeneratorConstants.DATAFIELD_NAME,
133 GeneratorConstants.DATAFIELD_TYPE.getDescriptor(), null, null);
134 }
135
136 private void createInitMethod() {
137 final int access = GeneratorConstants.INIT_METHOD_ACC;
138 final String name = GeneratorConstants.INIT_METHOD.getName();
139 final String desc = GeneratorConstants.INIT_METHOD.getDescriptor();
140 final GeneratorAdapter gen = new GeneratorAdapter(super.visitMethod(
141 access, name, desc, null, null), access, name, desc);
142
143 // Load the value of the static data field:
144 gen.getStatic(type, GeneratorConstants.DATAFIELD_NAME,
145 GeneratorConstants.DATAFIELD_TYPE);
146 gen.dup();
147
148 // Stack[1]: [[Z
149 // Stack[0]: [[Z
150
151 // Skip initialization when we already have a data array:
152 final Label alreadyInitialized = new Label();
153 gen.ifNonNull(alreadyInitialized);
154
155 // Stack[0]: [[Z
156
157 gen.pop();
158 final int size = genInitializeDataField(gen);
159
160 // Stack[0]: [[Z
161
162 // Return the method's block array:
163 gen.visitLabel(alreadyInitialized);
164 gen.loadArg(0);
165
166 // Stack[1]: I
167 // Stack[0]: [[Z
168
169 gen.arrayLoad(GeneratorConstants.BLOCK_ARR);
170
171 // Stack[0]: [Z
172
173 gen.returnValue();
174
175 gen.visitMaxs(Math.max(size, 2), 0); // Maximum local stack size is 2
176 gen.visitEnd();
177 }
178
179 /**
180 * Generates the byte code to initialize the static coverage data field
181 * within this class.
182 *
183 * The code will push the [[Z data array on the operand stack.
184 *
185 * @param gen
186 * generator to emit code to
187 */
188 private int genInitializeDataField(final GeneratorAdapter gen) {
189 final int size = runtime.generateDataAccessor(id, gen);
190
191 // Stack[0]: [[Z
192
193 gen.dup();
194
195 // Stack[1]: [[Z
196 // Stack[0]: [[Z
197
198 gen.putStatic(type, GeneratorConstants.DATAFIELD_NAME,
199 GeneratorConstants.DATAFIELD_TYPE);
200
201 // Stack[0]: [[Z
202
203 return Math.max(size, 2); // Maximum local stack size is 2
204 }
205
206 /**
207 * Ensures that the given member does not correspond to a internal member
208 * created by the instrumentation process. This would mean that the class
209 * has been instrumented twice.
210 *
211 * @param member
212 * name of the member to check
213 * @param instrMember
214 * name of a instrumentation member
215 * @throws IllegalStateException
216 * thrown if the member has the same name than the
217 * instrumentation member
218 */
219 private void assertNotInstrumented(final String member,
220 final String instrMember) throws IllegalStateException {
221 if (member.equals(instrMember)) {
222 throw new IllegalStateException(format(
223 "Class %s is already instrumented.", type.getClassName()));
224 }
225 }
226
227 /**
228 * Create a execution data structure according to the structure of this
229 * class and registers it with the runtime.
230 */
231 private void registerClass() {
232 final boolean[][] data = new boolean[blockCounters.size()][];
233 for (int blockIdx = 0; blockIdx < blockCounters.size(); blockIdx++) {
234 data[blockIdx] = new boolean[blockCounters.get(blockIdx)
235 .getBlockCount()];
236 }
237 runtime.registerClass(id, type.getInternalName(), data);
238 }
239
240}