• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*******************************************************************************
2  * Copyright (c) 2009, 2021 Mountainminds GmbH & Co. KG and Contributors
3  * This program and the accompanying materials are made available under
4  * the terms of the Eclipse Public License 2.0 which is available at
5  * http://www.eclipse.org/legal/epl-2.0
6  *
7  * SPDX-License-Identifier: EPL-2.0
8  *
9  * Contributors:
10  *    Marc R. Hoffmann - initial API and implementation
11  *
12  *******************************************************************************/
13 package org.jacoco.core.instr;
14 
15 import java.io.ByteArrayOutputStream;
16 import java.io.IOException;
17 import java.io.InputStream;
18 import java.io.OutputStream;
19 import java.util.zip.CRC32;
20 import java.util.zip.GZIPInputStream;
21 import java.util.zip.GZIPOutputStream;
22 import java.util.zip.ZipEntry;
23 import java.util.zip.ZipInputStream;
24 import java.util.zip.ZipOutputStream;
25 
26 import org.jacoco.core.internal.ContentTypeDetector;
27 import org.jacoco.core.internal.InputStreams;
28 import org.jacoco.core.internal.Pack200Streams;
29 import org.jacoco.core.internal.data.CRC64;
30 import org.jacoco.core.internal.flow.ClassProbesAdapter;
31 import org.jacoco.core.internal.instr.ClassInstrumenter;
32 import org.jacoco.core.internal.instr.IProbeArrayStrategy;
33 import org.jacoco.core.internal.instr.InstrSupport;
34 import org.jacoco.core.internal.instr.ProbeArrayStrategyFactory;
35 import org.jacoco.core.internal.instr.SignatureRemover;
36 import org.jacoco.core.runtime.IExecutionDataAccessorGenerator;
37 import org.objectweb.asm.ClassReader;
38 import org.objectweb.asm.ClassVisitor;
39 import org.objectweb.asm.ClassWriter;
40 
41 /**
42  * Several APIs to instrument Java class definitions for coverage tracing.
43  */
44 public class Instrumenter {
45 
46 	private final IExecutionDataAccessorGenerator accessorGenerator;
47 
48 	private final SignatureRemover signatureRemover;
49 
50 	/**
51 	 * Creates a new instance based on the given runtime.
52 	 *
53 	 * @param runtime
54 	 *            runtime used by the instrumented classes
55 	 */
Instrumenter(final IExecutionDataAccessorGenerator runtime)56 	public Instrumenter(final IExecutionDataAccessorGenerator runtime) {
57 		this.accessorGenerator = runtime;
58 		this.signatureRemover = new SignatureRemover();
59 	}
60 
61 	/**
62 	 * Determines whether signatures should be removed from JAR files. This is
63 	 * typically necessary as instrumentation modifies the class files and
64 	 * therefore invalidates existing JAR signatures. Default is
65 	 * <code>true</code>.
66 	 *
67 	 * @param flag
68 	 *            <code>true</code> if signatures should be removed
69 	 */
setRemoveSignatures(final boolean flag)70 	public void setRemoveSignatures(final boolean flag) {
71 		signatureRemover.setActive(flag);
72 	}
73 
instrument(final byte[] source)74 	private byte[] instrument(final byte[] source) {
75 		final long classId = CRC64.classId(source);
76 		final ClassReader reader = InstrSupport.classReaderFor(source);
77 		final ClassWriter writer = new ClassWriter(reader, 0) {
78 			@Override
79 			protected String getCommonSuperClass(final String type1,
80 					final String type2) {
81 				throw new IllegalStateException();
82 			}
83 		};
84 		final IProbeArrayStrategy strategy = ProbeArrayStrategyFactory
85 				.createFor(classId, reader, accessorGenerator);
86 		final int version = InstrSupport.getMajorVersion(reader);
87 		final ClassVisitor visitor = new ClassProbesAdapter(
88 				new ClassInstrumenter(strategy, writer),
89 				InstrSupport.needsFrames(version));
90 		reader.accept(visitor, ClassReader.EXPAND_FRAMES);
91 		return writer.toByteArray();
92 	}
93 
94 	/**
95 	 * Creates a instrumented version of the given class if possible.
96 	 *
97 	 * @param buffer
98 	 *            definition of the class
99 	 * @param name
100 	 *            a name used for exception messages
101 	 * @return instrumented definition
102 	 * @throws IOException
103 	 *             if the class can't be instrumented
104 	 */
instrument(final byte[] buffer, final String name)105 	public byte[] instrument(final byte[] buffer, final String name)
106 			throws IOException {
107 		try {
108 			return instrument(buffer);
109 		} catch (final RuntimeException e) {
110 			throw instrumentError(name, e);
111 		}
112 	}
113 
114 	/**
115 	 * Creates a instrumented version of the given class if possible. The
116 	 * provided {@link InputStream} is not closed by this method.
117 	 *
118 	 * @param input
119 	 *            stream to read class definition from
120 	 * @param name
121 	 *            a name used for exception messages
122 	 * @return instrumented definition
123 	 * @throws IOException
124 	 *             if reading data from the stream fails or the class can't be
125 	 *             instrumented
126 	 */
instrument(final InputStream input, final String name)127 	public byte[] instrument(final InputStream input, final String name)
128 			throws IOException {
129 		final byte[] bytes;
130 		try {
131 			bytes = InputStreams.readFully(input);
132 		} catch (final IOException e) {
133 			throw instrumentError(name, e);
134 		}
135 		return instrument(bytes, name);
136 	}
137 
138 	/**
139 	 * Creates a instrumented version of the given class file. The provided
140 	 * {@link InputStream} and {@link OutputStream} instances are not closed by
141 	 * this method.
142 	 *
143 	 * @param input
144 	 *            stream to read class definition from
145 	 * @param output
146 	 *            stream to write the instrumented version of the class to
147 	 * @param name
148 	 *            a name used for exception messages
149 	 * @throws IOException
150 	 *             if reading data from the stream fails or the class can't be
151 	 *             instrumented
152 	 */
instrument(final InputStream input, final OutputStream output, final String name)153 	public void instrument(final InputStream input, final OutputStream output,
154 			final String name) throws IOException {
155 		output.write(instrument(input, name));
156 	}
157 
instrumentError(final String name, final Exception cause)158 	private IOException instrumentError(final String name,
159 			final Exception cause) {
160 		final IOException ex = new IOException(
161 				String.format("Error while instrumenting %s.", name));
162 		ex.initCause(cause);
163 		return ex;
164 	}
165 
166 	/**
167 	 * Creates a instrumented version of the given resource depending on its
168 	 * type. Class files and the content of archive files are instrumented. All
169 	 * other files are copied without modification. The provided
170 	 * {@link InputStream} and {@link OutputStream} instances are not closed by
171 	 * this method.
172 	 *
173 	 * @param input
174 	 *            stream to contents from
175 	 * @param output
176 	 *            stream to write the instrumented version of the contents
177 	 * @param name
178 	 *            a name used for exception messages
179 	 * @return number of instrumented classes
180 	 * @throws IOException
181 	 *             if reading data from the stream fails or a class can't be
182 	 *             instrumented
183 	 */
instrumentAll(final InputStream input, final OutputStream output, final String name)184 	public int instrumentAll(final InputStream input, final OutputStream output,
185 			final String name) throws IOException {
186 		final ContentTypeDetector detector;
187 		try {
188 			detector = new ContentTypeDetector(input);
189 		} catch (final IOException e) {
190 			throw instrumentError(name, e);
191 		}
192 		switch (detector.getType()) {
193 		case ContentTypeDetector.CLASSFILE:
194 			instrument(detector.getInputStream(), output, name);
195 			return 1;
196 		case ContentTypeDetector.ZIPFILE:
197 			return instrumentZip(detector.getInputStream(), output, name);
198 		case ContentTypeDetector.GZFILE:
199 			return instrumentGzip(detector.getInputStream(), output, name);
200 		case ContentTypeDetector.PACK200FILE:
201 			return instrumentPack200(detector.getInputStream(), output, name);
202 		default:
203 			copy(detector.getInputStream(), output, name);
204 			return 0;
205 		}
206 	}
207 
instrumentZip(final InputStream input, final OutputStream output, final String name)208 	private int instrumentZip(final InputStream input,
209 			final OutputStream output, final String name) throws IOException {
210 		final ZipInputStream zipin = new ZipInputStream(input);
211 		final ZipOutputStream zipout = new ZipOutputStream(output);
212 		ZipEntry entry;
213 		int count = 0;
214 		while ((entry = nextEntry(zipin, name)) != null) {
215 			final String entryName = entry.getName();
216 			if (signatureRemover.removeEntry(entryName)) {
217 				continue;
218 			}
219 
220 			final ZipEntry newEntry = new ZipEntry(entryName);
221 			newEntry.setMethod(entry.getMethod());
222 			switch (entry.getMethod()) {
223 			case ZipEntry.DEFLATED:
224 				zipout.putNextEntry(newEntry);
225 				count += filterOrInstrument(zipin, zipout, name, entryName);
226 				break;
227 			case ZipEntry.STORED:
228 				// Uncompressed entries must be processed in-memory to calculate
229 				// mandatory entry size and CRC
230 				final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
231 				count += filterOrInstrument(zipin, buffer, name, entryName);
232 				final byte[] bytes = buffer.toByteArray();
233 				newEntry.setSize(bytes.length);
234 				newEntry.setCompressedSize(bytes.length);
235 				newEntry.setCrc(crc(bytes));
236 				zipout.putNextEntry(newEntry);
237 				zipout.write(bytes);
238 				break;
239 			default:
240 				throw new AssertionError(entry.getMethod());
241 			}
242 			zipout.closeEntry();
243 		}
244 		zipout.finish();
245 		return count;
246 	}
247 
filterOrInstrument(final InputStream in, final OutputStream out, final String name, final String entryName)248 	private int filterOrInstrument(final InputStream in, final OutputStream out,
249 			final String name, final String entryName) throws IOException {
250 		if (signatureRemover.filterEntry(entryName, in, out)) {
251 			return 0;
252 		} else {
253 			return instrumentAll(in, out, name + "@" + entryName);
254 		}
255 	}
256 
crc(final byte[] data)257 	private static long crc(final byte[] data) {
258 		final CRC32 crc = new CRC32();
259 		crc.update(data);
260 		return crc.getValue();
261 	}
262 
nextEntry(final ZipInputStream input, final String location)263 	private ZipEntry nextEntry(final ZipInputStream input,
264 			final String location) throws IOException {
265 		try {
266 			return input.getNextEntry();
267 		} catch (final IOException e) {
268 			throw instrumentError(location, e);
269 		}
270 	}
271 
instrumentGzip(final InputStream input, final OutputStream output, final String name)272 	private int instrumentGzip(final InputStream input,
273 			final OutputStream output, final String name) throws IOException {
274 		final GZIPInputStream gzipInputStream;
275 		try {
276 			gzipInputStream = new GZIPInputStream(input);
277 		} catch (final IOException e) {
278 			throw instrumentError(name, e);
279 		}
280 		final GZIPOutputStream gzout = new GZIPOutputStream(output);
281 		final int count = instrumentAll(gzipInputStream, gzout, name);
282 		gzout.finish();
283 		return count;
284 	}
285 
instrumentPack200(final InputStream input, final OutputStream output, final String name)286 	private int instrumentPack200(final InputStream input,
287 			final OutputStream output, final String name) throws IOException {
288 		final InputStream unpackedInput;
289 		try {
290 			unpackedInput = Pack200Streams.unpack(input);
291 		} catch (final IOException e) {
292 			throw instrumentError(name, e);
293 		}
294 		final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
295 		final int count = instrumentAll(unpackedInput, buffer, name);
296 		Pack200Streams.pack(buffer.toByteArray(), output);
297 		return count;
298 	}
299 
copy(final InputStream input, final OutputStream output, final String name)300 	private void copy(final InputStream input, final OutputStream output,
301 			final String name) throws IOException {
302 		final byte[] buffer = new byte[1024];
303 		int len;
304 		while ((len = read(input, buffer, name)) != -1) {
305 			output.write(buffer, 0, len);
306 		}
307 	}
308 
read(final InputStream input, final byte[] buffer, final String name)309 	private int read(final InputStream input, final byte[] buffer,
310 			final String name) throws IOException {
311 		try {
312 			return input.read(buffer);
313 		} catch (final IOException e) {
314 			throw instrumentError(name, e);
315 		}
316 	}
317 
318 }
319