ClassInstrumenter.java
1/*******************************************************************************
2 * Copyright (c) 2009, 2010 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 org.jacoco.core.runtime.IRuntime;
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 IRuntime runtime;
39
40 private String className;
41
42 /**
43 * Emits a instrumented version of this class to the given class visitor.
44 *
45 * @param id
46 * unique identifier given to this class
47 * @param runtime
48 * this runtime will be used for instrumentation
49 * @param cv
50 * next delegate in the visitor chain will receive the
51 * instrumented class
52 */
53 public ClassInstrumenter(final long id, final IRuntime runtime,
54 final ClassVisitor cv) {
55 this.delegate = cv;
56 this.id = id;
57 this.runtime = runtime;
58 }
59
60 public void visit(final int version, final int access, final String name,
61 final String signature, final String superName,
62 final String[] interfaces) {
63 this.className = name;
64 delegate.visit(version, access, name, signature, superName, interfaces);
65 }
66
67 public FieldVisitor visitField(final int access, final String name,
68 final String desc, final String signature, final Object value) {
69 assertNotInstrumented(name, GeneratorConstants.DATAFIELD_NAME);
70 return delegate.visitField(access, name, desc, signature, value);
71 }
72
73 @Override
74 protected IBlockMethodVisitor visitNonAbstractMethod(final int access,
75 final String name, final String desc, final String signature,
76 final String[] exceptions) {
77
78 assertNotInstrumented(name, GeneratorConstants.INITMETHOD_NAME);
79
80 final MethodVisitor mv = delegate.visitMethod(access, name, desc,
81 signature, exceptions);
82
83 if (mv == null) {
84 return null;
85 }
86 return new MethodInstrumenter(mv, access, name, desc, className);
87 }
88
89 @Override
90 protected MethodVisitor visitAbstractMethod(final int access,
91 final String name, final String desc, final String signature,
92 final String[] exceptions) {
93 return delegate.visitMethod(access, name, desc, signature, exceptions);
94 }
95
96 public void visitEnd() {
97 createDataField();
98 createInitMethod();
99 registerClass();
100 delegate.visitEnd();
101 }
102
103 private void createDataField() {
104 delegate.visitField(GeneratorConstants.DATAFIELD_ACC,
105 GeneratorConstants.DATAFIELD_NAME,
106 GeneratorConstants.PROBEDATA_TYPE.getDescriptor(), null, null);
107 }
108
109 private void createInitMethod() {
110 final int access = GeneratorConstants.INITMETHOD_ACC;
111 final MethodVisitor mv = delegate.visitMethod(access,
112 GeneratorConstants.INITMETHOD_NAME,
113 GeneratorConstants.INITMETHOD_DESC, null, null);
114
115 // Load the value of the static data field:
116 mv.visitFieldInsn(Opcodes.GETSTATIC, className,
117 GeneratorConstants.DATAFIELD_NAME,
118 GeneratorConstants.PROBEDATA_TYPE.getDescriptor());
119 mv.visitInsn(Opcodes.DUP);
120
121 // Stack[1]: [Z
122 // Stack[0]: [Z
123
124 // Skip initialization when we already have a data array:
125 final Label alreadyInitialized = new Label();
126 mv.visitJumpInsn(Opcodes.IFNONNULL, alreadyInitialized);
127
128 // Stack[0]: [Z
129
130 mv.visitInsn(Opcodes.POP);
131 final int size = genInitializeDataField(mv);
132
133 // Stack[0]: [Z
134
135 // Return the method's block array:
136 mv.visitLabel(alreadyInitialized);
137 mv.visitInsn(Opcodes.ARETURN);
138
139 mv.visitMaxs(Math.max(size, 2), 1); // Maximum local stack size is 2
140 mv.visitEnd();
141 }
142
143 /**
144 * Generates the byte code to initialize the static coverage data field
145 * within this class.
146 *
147 * The code will push the [[Z data array on the operand stack.
148 *
149 * @param mv
150 * generator to emit code to
151 */
152 private int genInitializeDataField(final MethodVisitor mv) {
153 final int size = runtime.generateDataAccessor(id, mv);
154
155 // Stack[0]: [Z
156
157 mv.visitInsn(Opcodes.DUP);
158
159 // Stack[1]: [Z
160 // Stack[0]: [Z
161
162 mv.visitFieldInsn(Opcodes.PUTSTATIC, className,
163 GeneratorConstants.DATAFIELD_NAME,
164 GeneratorConstants.PROBEDATA_TYPE.getDescriptor());
165
166 // Stack[0]: [Z
167
168 return Math.max(size, 2); // Maximum local stack size is 2
169 }
170
171 /**
172 * Ensures that the given member does not correspond to a internal member
173 * created by the instrumentation process. This would mean that the class
174 * has been instrumented twice.
175 *
176 * @param member
177 * name of the member to check
178 * @param instrMember
179 * name of a instrumentation member
180 * @throws IllegalStateException
181 * thrown if the member has the same name than the
182 * instrumentation member
183 */
184 private void assertNotInstrumented(final String member,
185 final String instrMember) throws IllegalStateException {
186 if (member.equals(instrMember)) {
187 throw new IllegalStateException(format(
188 "Class %s is already instrumented.", className));
189 }
190 }
191
192 /**
193 * Create a execution data structure according to the structure of this
194 * class and registers it with the runtime.
195 */
196 private void registerClass() {
197 final boolean[] data = new boolean[getProbeCount()];
198 runtime.registerClass(id, className, data);
199 }
200
201 // Methods we simply delegate:
202
203 public AnnotationVisitor visitAnnotation(final String desc,
204 final boolean visible) {
205 return delegate.visitAnnotation(desc, visible);
206 }
207
208 public void visitAttribute(final Attribute attr) {
209 delegate.visitAttribute(attr);
210 }
211
212 public void visitInnerClass(final String name, final String outerName,
213 final String innerName, final int access) {
214 delegate.visitInnerClass(name, outerName, innerName, access);
215 }
216
217 public void visitOuterClass(final String owner, final String name,
218 final String desc) {
219 delegate.visitOuterClass(owner, name, desc);
220 }
221
222 public void visitSource(final String source, final String debug) {
223 delegate.visitSource(source, debug);
224 }
225
226}