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 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.Type;
25import org.objectweb.asm.commons.GeneratorAdapter;
26
27/**
28 * Adapter that instruments a class for coverage tracing.
29 *
30 * @author Marc R. Hoffmann
31 * @version $Revision: $
32 */
33public class ClassInstrumenter extends BlockClassAdapter {
34
35 private final ClassVisitor delegate;
36
37 private final long id;
38
39 private final IRuntime runtime;
40
41 private Type type;
42
43 /**
44 * Emits a instrumented version of this class to the given class visitor.
45 *
46 * @param id
47 * unique identifier given to this class
48 * @param runtime
49 * this runtime will be used for instrumentation
50 * @param cv
51 * next delegate in the visitor chain will receive the
52 * instrumented class
53 */
54 public ClassInstrumenter(final long id, final IRuntime runtime,
55 final ClassVisitor cv) {
56 this.delegate = cv;
57 this.id = id;
58 this.runtime = runtime;
59 }
60
61 public void visit(final int version, final int access, final String name,
62 final String signature, final String superName,
63 final String[] interfaces) {
64 this.type = Type.getObjectType(name);
65 delegate.visit(version, access, name, signature, superName, interfaces);
66 }
67
68 public FieldVisitor visitField(final int access, final String name,
69 final String desc, final String signature, final Object value) {
70 assertNotInstrumented(name, GeneratorConstants.DATAFIELD_NAME);
71 return delegate.visitField(access, name, desc, signature, value);
72 }
73
74 @Override
75 protected IBlockMethodVisitor visitNonAbstractMethod(final int access,
76 final String name, final String desc, final String signature,
77 final String[] exceptions) {
78
79 assertNotInstrumented(name, GeneratorConstants.INIT_METHOD.getName());
80
81 final MethodVisitor mv = delegate.visitMethod(access, name, desc,
82 signature, exceptions);
83
84 if (mv == null) {
85 return null;
86 }
87 return new MethodInstrumenter(mv, access, name, desc, type);
88 }
89
90 @Override
91 protected MethodVisitor visitAbstractMethod(final int access,
92 final String name, final String desc, final String signature,
93 final String[] exceptions) {
94 return delegate.visitMethod(access, name, desc, signature, exceptions);
95 }
96
97 public void visitEnd() {
98 createDataField();
99 createInitMethod();
100 registerClass();
101 delegate.visitEnd();
102 }
103
104 private void createDataField() {
105 delegate.visitField(GeneratorConstants.DATAFIELD_ACC,
106 GeneratorConstants.DATAFIELD_NAME,
107 GeneratorConstants.PROBEDATA_TYPE.getDescriptor(), null, null);
108 }
109
110 private void createInitMethod() {
111 final int access = GeneratorConstants.INIT_METHOD_ACC;
112 final String name = GeneratorConstants.INIT_METHOD.getName();
113 final String desc = GeneratorConstants.INIT_METHOD.getDescriptor();
114 final GeneratorAdapter gen = new GeneratorAdapter(delegate.visitMethod(
115 access, name, desc, null, null), access, name, desc);
116
117 // Load the value of the static data field:
118 gen.getStatic(type, GeneratorConstants.DATAFIELD_NAME,
119 GeneratorConstants.PROBEDATA_TYPE);
120 gen.dup();
121
122 // Stack[1]: [Z
123 // Stack[0]: [Z
124
125 // Skip initialization when we already have a data array:
126 final Label alreadyInitialized = new Label();
127 gen.ifNonNull(alreadyInitialized);
128
129 // Stack[0]: [Z
130
131 gen.pop();
132 final int size = genInitializeDataField(gen);
133
134 // Stack[0]: [Z
135
136 // Return the method's block array:
137 gen.visitLabel(alreadyInitialized);
138 gen.returnValue();
139
140 gen.visitMaxs(Math.max(size, 2), 0); // Maximum local stack size is 2
141 gen.visitEnd();
142 }
143
144 /**
145 * Generates the byte code to initialize the static coverage data field
146 * within this class.
147 *
148 * The code will push the [[Z data array on the operand stack.
149 *
150 * @param gen
151 * generator to emit code to
152 */
153 private int genInitializeDataField(final GeneratorAdapter gen) {
154 final int size = runtime.generateDataAccessor(id, gen);
155
156 // Stack[0]: [Z
157
158 gen.dup();
159
160 // Stack[1]: [Z
161 // Stack[0]: [Z
162
163 gen.putStatic(type, GeneratorConstants.DATAFIELD_NAME,
164 GeneratorConstants.PROBEDATA_TYPE);
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.", type.getClassName()));
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, type.getInternalName(), 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}