ClassInstrumenter.java
1/*******************************************************************************
2 * Copyright (c) 2009, 2010 Mountainminds GmbH & Co. KG and Contributors
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 org.jacoco.core.runtime.IExecutionDataAccessorGenerator;
18import org.objectweb.asm.AnnotationVisitor;
19import org.objectweb.asm.Attribute;
20import org.objectweb.asm.ClassVisitor;
21import org.objectweb.asm.FieldVisitor;
22import org.objectweb.asm.Label;
23import org.objectweb.asm.MethodVisitor;
24import org.objectweb.asm.Opcodes;
25
26/**
27 * Adapter that instruments a class for coverage tracing.
28 *
29 * @author Marc R. Hoffmann
30 * @version $Revision: $
31 */
32public class ClassInstrumenter extends BlockClassAdapter {
33
34 private final ClassVisitor delegate;
35
36 private final long id;
37
38 private final IExecutionDataAccessorGenerator accessorGenerator;
39
40 private IProbeArrayStrategy probeArrayStrategy;
41
42 private String className;
43
44 /**
45 * Emits a instrumented version of this class to the given class visitor.
46 *
47 * @param id
48 * unique identifier given to this class
49 * @param accessorGenerator
50 * this generator will be used for instrumentation
51 * @param cv
52 * next delegate in the visitor chain will receive the
53 * instrumented class
54 */
55 public ClassInstrumenter(final long id,
56 final IExecutionDataAccessorGenerator accessorGenerator,
57 final ClassVisitor cv) {
58 this.delegate = cv;
59 this.id = id;
60 this.accessorGenerator = accessorGenerator;
61 }
62
63 public void visit(final int version, final int access, final String name,
64 final String signature, final String superName,
65 final String[] interfaces) {
66 this.className = name;
67 if ((access & Opcodes.ACC_INTERFACE) == 0) {
68 this.probeArrayStrategy = new ClassTypeStrategy();
69 } else {
70 this.probeArrayStrategy = new InterfaceTypeStrategy();
71 }
72 delegate.visit(version, access, name, signature, superName, interfaces);
73 }
74
75 public FieldVisitor visitField(final int access, final String name,
76 final String desc, final String signature, final Object value) {
77 assertNotInstrumented(name, GeneratorConstants.DATAFIELD_NAME);
78 return delegate.visitField(access, name, desc, signature, value);
79 }
80
81 @Override
82 protected IBlockMethodVisitor visitNonAbstractMethod(final int access,
83 final String name, final String desc, final String signature,
84 final String[] exceptions) {
85
86 assertNotInstrumented(name, GeneratorConstants.INITMETHOD_NAME);
87
88 final MethodVisitor mv = delegate.visitMethod(access, name, desc,
89 signature, exceptions);
90
91 if (mv == null) {
92 return null;
93 }
94 return new MethodInstrumenter(mv, access, name, desc,
95 probeArrayStrategy);
96 }
97
98 @Override
99 protected MethodVisitor visitAbstractMethod(final int access,
100 final String name, final String desc, final String signature,
101 final String[] exceptions) {
102 return delegate.visitMethod(access, name, desc, signature, exceptions);
103 }
104
105 public void visitEnd() {
106 probeArrayStrategy.addMembers(delegate);
107 delegate.visitEnd();
108 }
109
110 /**
111 * Ensures that the given member does not correspond to a internal member
112 * created by the instrumentation process. This would mean that the class
113 * has been instrumented twice.
114 *
115 * @param member
116 * name of the member to check
117 * @param instrMember
118 * name of a instrumentation member
119 * @throws IllegalStateException
120 * thrown if the member has the same name than the
121 * instrumentation member
122 */
123 private void assertNotInstrumented(final String member,
124 final String instrMember) throws IllegalStateException {
125 if (member.equals(instrMember)) {
126 throw new IllegalStateException(format(
127 "Class %s is already instrumented.", className));
128 }
129 }
130
131 // Methods we simply delegate:
132
133 public AnnotationVisitor visitAnnotation(final String desc,
134 final boolean visible) {
135 return delegate.visitAnnotation(desc, visible);
136 }
137
138 public void visitAttribute(final Attribute attr) {
139 delegate.visitAttribute(attr);
140 }
141
142 public void visitInnerClass(final String name, final String outerName,
143 final String innerName, final int access) {
144 delegate.visitInnerClass(name, outerName, innerName, access);
145 }
146
147 public void visitOuterClass(final String owner, final String name,
148 final String desc) {
149 delegate.visitOuterClass(owner, name, desc);
150 }
151
152 public void visitSource(final String source, final String debug) {
153 delegate.visitSource(source, debug);
154 }
155
156 // === probe array strategies ===
157
158 private class ClassTypeStrategy implements IProbeArrayStrategy {
159
160 public int pushInstance(final MethodVisitor mv) {
161 mv.visitMethodInsn(Opcodes.INVOKESTATIC, className,
162 GeneratorConstants.INITMETHOD_NAME,
163 GeneratorConstants.INITMETHOD_DESC);
164 return 1;
165 }
166
167 public void addMembers(final ClassVisitor delegate) {
168 createDataField();
169 createInitMethod();
170 }
171
172 private void createDataField() {
173 delegate.visitField(GeneratorConstants.DATAFIELD_ACC,
174 GeneratorConstants.DATAFIELD_NAME,
175 GeneratorConstants.PROBEDATA_TYPE.getDescriptor(), null,
176 null);
177 }
178
179 private void createInitMethod() {
180 final MethodVisitor mv = delegate.visitMethod(
181 GeneratorConstants.INITMETHOD_ACC,
182 GeneratorConstants.INITMETHOD_NAME,
183 GeneratorConstants.INITMETHOD_DESC, null, null);
184
185 // Load the value of the static data field:
186 mv.visitFieldInsn(Opcodes.GETSTATIC, className,
187 GeneratorConstants.DATAFIELD_NAME,
188 GeneratorConstants.PROBEDATA_TYPE.getDescriptor());
189 mv.visitInsn(Opcodes.DUP);
190
191 // Stack[1]: [Z
192 // Stack[0]: [Z
193
194 // Skip initialization when we already have a data array:
195 final Label alreadyInitialized = new Label();
196 mv.visitJumpInsn(Opcodes.IFNONNULL, alreadyInitialized);
197
198 // Stack[0]: [Z
199
200 mv.visitInsn(Opcodes.POP);
201 final int size = genInitializeDataField(mv);
202
203 // Stack[0]: [Z
204
205 // Return the method's block array:
206 mv.visitLabel(alreadyInitialized);
207 mv.visitInsn(Opcodes.ARETURN);
208
209 mv.visitMaxs(Math.max(size, 2), 1); // Maximum local stack size is 2
210 mv.visitEnd();
211 }
212
213 /**
214 * Generates the byte code to initialize the static coverage data field
215 * within this class.
216 *
217 * The code will push the [[Z data array on the operand stack.
218 *
219 * @param mv
220 * generator to emit code to
221 */
222 private int genInitializeDataField(final MethodVisitor mv) {
223 final int size = accessorGenerator.generateDataAccessor(id,
224 className, getProbeCount(), mv);
225
226 // Stack[0]: [Z
227
228 mv.visitInsn(Opcodes.DUP);
229
230 // Stack[1]: [Z
231 // Stack[0]: [Z
232
233 mv.visitFieldInsn(Opcodes.PUTSTATIC, className,
234 GeneratorConstants.DATAFIELD_NAME,
235 GeneratorConstants.PROBEDATA_TYPE.getDescriptor());
236
237 // Stack[0]: [Z
238
239 return Math.max(size, 2); // Maximum local stack size is 2
240 }
241 }
242
243 private class InterfaceTypeStrategy implements IProbeArrayStrategy {
244
245 public int pushInstance(final MethodVisitor mv) {
246 // TODO As we don't know the actual probe count at this point in
247 // time use a hard coded value of 64. This is typically be way too
248 // big and will break static initializers in interfaces with more
249 // than 64 blocks. So this has to be replaced with the actual probe
250 // count.
251 final int size = accessorGenerator.generateDataAccessor(id,
252 className, 64, mv);
253 return size;
254 }
255
256 public void addMembers(final ClassVisitor delegate) {
257 }
258 }
259
260}