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 *******************************************************************************/
12package org.jacoco.core.instr;
13
14import static java.lang.String.format;
15
16import org.jacoco.core.runtime.IExecutionDataAccessorGenerator;
17import org.objectweb.asm.ClassAdapter;
18import org.objectweb.asm.ClassVisitor;
19import org.objectweb.asm.FieldVisitor;
20import org.objectweb.asm.Label;
21import org.objectweb.asm.MethodVisitor;
22import org.objectweb.asm.Opcodes;
23
24/**
25 * Adapter that instruments a class for coverage tracing.
26 *
27 * @author Marc R. Hoffmann
28 * @version 0.4.1.20101007204400
29 */
30class ClassInstrumenter extends ClassAdapter implements IBlockClassVisitor {
31
32 private static final Object[] STACK_ARRZ = new Object[] { InstrSupport.DATAFIELD_DESC };
33
34 private final long id;
35
36 private final IExecutionDataAccessorGenerator accessorGenerator;
37
38 private IProbeArrayStrategy probeArrayStrategy;
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 accessorGenerator
48 * this generator 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,
54 final IExecutionDataAccessorGenerator accessorGenerator,
55 final ClassVisitor cv) {
56 super(cv);
57 this.id = id;
58 this.accessorGenerator = accessorGenerator;
59 }
60
61 @Override
62 public void visit(final int version, final int access, final String name,
63 final String signature, final String superName,
64 final String[] interfaces) {
65 this.className = name;
66 if ((access & Opcodes.ACC_INTERFACE) == 0) {
67 this.probeArrayStrategy = new ClassTypeStrategy();
68 } else {
69 this.probeArrayStrategy = new InterfaceTypeStrategy();
70 }
71 super.visit(version, access, name, signature, superName, interfaces);
72 }
73
74 @Override
75 public FieldVisitor visitField(final int access, final String name,
76 final String desc, final String signature, final Object value) {
77 assertNotInstrumented(name, InstrSupport.DATAFIELD_NAME);
78 return super.visitField(access, name, desc, signature, value);
79 }
80
81 @Override
82 public IBlockMethodVisitor visitMethod(final int access, final String name,
83 final String desc, final String signature, final String[] exceptions) {
84
85 assertNotInstrumented(name, InstrSupport.INITMETHOD_NAME);
86
87 final MethodVisitor mv = super.visitMethod(access, name, desc,
88 signature, exceptions);
89
90 if (mv == null) {
91 return null;
92 }
93 return new MethodInstrumenter(mv, access, desc, probeArrayStrategy);
94 }
95
96 public void visitTotalProbeCount(final int count) {
97 probeArrayStrategy.addMembers(cv, count);
98 }
99
100 /**
101 * Ensures that the given member does not correspond to a internal member
102 * created by the instrumentation process. This would mean that the class
103 * has been instrumented twice.
104 *
105 * @param member
106 * name of the member to check
107 * @param instrMember
108 * name of a instrumentation member
109 * @throws IllegalStateException
110 * thrown if the member has the same name than the
111 * instrumentation member
112 */
113 private void assertNotInstrumented(final String member,
114 final String instrMember) throws IllegalStateException {
115 if (member.equals(instrMember)) {
116 throw new IllegalStateException(format(
117 "Class %s is already instrumented.", className));
118 }
119 }
120
121 // === probe array strategies ===
122
123 private class ClassTypeStrategy implements IProbeArrayStrategy {
124
125 public int pushInstance(final MethodVisitor mv) {
126 mv.visitMethodInsn(Opcodes.INVOKESTATIC, className,
127 InstrSupport.INITMETHOD_NAME, InstrSupport.INITMETHOD_DESC);
128 return 1;
129 }
130
131 public void addMembers(final ClassVisitor delegate, final int probeCount) {
132 createDataField();
133 createInitMethod(probeCount);
134 }
135
136 private void createDataField() {
137 cv.visitField(InstrSupport.DATAFIELD_ACC,
138 InstrSupport.DATAFIELD_NAME, InstrSupport.DATAFIELD_DESC,
139 null, null);
140 }
141
142 private void createInitMethod(final int probeCount) {
143 final MethodVisitor mv = cv.visitMethod(
144 InstrSupport.INITMETHOD_ACC, InstrSupport.INITMETHOD_NAME,
145 InstrSupport.INITMETHOD_DESC, null, null);
146
147 // Load the value of the static data field:
148 mv.visitFieldInsn(Opcodes.GETSTATIC, className,
149 InstrSupport.DATAFIELD_NAME, InstrSupport.DATAFIELD_DESC);
150 mv.visitInsn(Opcodes.DUP);
151
152 // Stack[1]: [Z
153 // Stack[0]: [Z
154
155 // Skip initialization when we already have a data array:
156 final Label alreadyInitialized = new Label();
157 mv.visitJumpInsn(Opcodes.IFNONNULL, alreadyInitialized);
158
159 // Stack[0]: [Z
160
161 mv.visitInsn(Opcodes.POP);
162 final int size = genInitializeDataField(mv, probeCount);
163
164 // Stack[0]: [Z
165
166 // Return the method's block array:
167 mv.visitFrame(Opcodes.F_FULL, 0, new Object[0], 1, STACK_ARRZ);
168 mv.visitLabel(alreadyInitialized);
169 mv.visitInsn(Opcodes.ARETURN);
170
171 mv.visitMaxs(Math.max(size, 2), 1); // Maximum local stack size is 2
172 mv.visitEnd();
173 }
174
175 /**
176 * Generates the byte code to initialize the static coverage data field
177 * within this class.
178 *
179 * The code will push the [Z data array on the operand stack.
180 *
181 * @param mv
182 * generator to emit code to
183 */
184 private int genInitializeDataField(final MethodVisitor mv,
185 final int probeCount) {
186 final int size = accessorGenerator.generateDataAccessor(id,
187 className, probeCount, mv);
188
189 // Stack[0]: [Z
190
191 mv.visitInsn(Opcodes.DUP);
192
193 // Stack[1]: [Z
194 // Stack[0]: [Z
195
196 mv.visitFieldInsn(Opcodes.PUTSTATIC, className,
197 InstrSupport.DATAFIELD_NAME, InstrSupport.DATAFIELD_DESC);
198
199 // Stack[0]: [Z
200
201 return Math.max(size, 2); // Maximum local stack size is 2
202 }
203 }
204
205 private class InterfaceTypeStrategy implements IProbeArrayStrategy {
206
207 public int pushInstance(final MethodVisitor mv) {
208 // TODO As we don't know the actual probe count at this point in
209 // time use a hard coded value of 64. This is typically be way too
210 // big and will break static initializers in interfaces with more
211 // than 64 blocks. So this has to be replaced with the actual probe
212 // count.
213 final int size = accessorGenerator.generateDataAccessor(id,
214 className, 64, mv);
215 return size;
216 }
217
218 public void addMembers(final ClassVisitor delegate, final int probeCount) {
219 }
220
221 }
222
223}