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