• 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.analysis;
14 
15 import java.io.File;
16 import java.io.FileInputStream;
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.util.StringTokenizer;
20 import java.util.zip.GZIPInputStream;
21 import java.util.zip.ZipEntry;
22 import java.util.zip.ZipInputStream;
23 
24 import org.jacoco.core.data.IExecutionData;
25 import org.jacoco.core.data.ExecutionDataStore;
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.analysis.ClassAnalyzer;
30 import org.jacoco.core.internal.analysis.ClassCoverageImpl;
31 import org.jacoco.core.internal.analysis.StringPool;
32 import org.jacoco.core.internal.data.CRC64;
33 import org.jacoco.core.internal.flow.ClassProbesAdapter;
34 import org.jacoco.core.internal.instr.InstrSupport;
35 import org.objectweb.asm.ClassReader;
36 import org.objectweb.asm.ClassVisitor;
37 import org.objectweb.asm.Opcodes;
38 
39 /**
40  * An {@link Analyzer} instance processes a set of Java class files and
41  * calculates coverage data for them. For each class file the result is reported
42  * to a given {@link ICoverageVisitor} instance. In addition the
43  * {@link Analyzer} requires a {@link ExecutionDataStore} instance that holds
44  * the execution data for the classes to analyze. The {@link Analyzer} offers
45  * several methods to analyze classes from a variety of sources.
46  */
47 public class Analyzer {
48 
49 	private final ExecutionDataStore executionData;
50 
51 	private final ICoverageVisitor coverageVisitor;
52 
53 	private final StringPool stringPool;
54 
55 	/**
56 	 * Creates a new analyzer reporting to the given output.
57 	 *
58 	 * @param executionData
59 	 *            execution data
60 	 * @param coverageVisitor
61 	 *            the output instance that will coverage data for every analyzed
62 	 *            class
63 	 */
Analyzer(final ExecutionDataStore executionData, final ICoverageVisitor coverageVisitor)64 	public Analyzer(final ExecutionDataStore executionData,
65 			final ICoverageVisitor coverageVisitor) {
66 		this.executionData = executionData;
67 		this.coverageVisitor = coverageVisitor;
68 		this.stringPool = new StringPool();
69 	}
70 
71 	/**
72 	 * Creates an ASM class visitor for analysis.
73 	 *
74 	 * @param classid
75 	 *            id of the class calculated with {@link CRC64}
76 	 * @param className
77 	 *            VM name of the class
78 	 * @return ASM visitor to write class definition to
79 	 */
createAnalyzingVisitor(final long classid, final String className)80 	private ClassVisitor createAnalyzingVisitor(final long classid,
81 			final String className) {
82 		// BEGIN android-change
83 		final IExecutionData data = executionData.get(classid);
84 		final boolean[] probes;
85 		final boolean noMatch;
86 		if (data == null) {
87 			probes = null;
88 			noMatch = executionData.contains(className);
89 		} else {
90 			probes = data.getProbesCopy();
91 			noMatch = false;
92 		}
93 		// END android-change
94 		final ClassCoverageImpl coverage = new ClassCoverageImpl(className,
95 				classid, noMatch);
96 		final ClassAnalyzer analyzer = new ClassAnalyzer(coverage, probes,
97 				stringPool) {
98 			@Override
99 			public void visitEnd() {
100 				super.visitEnd();
101 				coverageVisitor.visitCoverage(coverage);
102 			}
103 		};
104 		return new ClassProbesAdapter(analyzer, false);
105 	}
106 
analyzeClass(final byte[] source)107 	private void analyzeClass(final byte[] source) {
108 		final long classId = CRC64.classId(source);
109 		final ClassReader reader = InstrSupport.classReaderFor(source);
110 		if ((reader.getAccess() & Opcodes.ACC_MODULE) != 0) {
111 			return;
112 		}
113 		if ((reader.getAccess() & Opcodes.ACC_SYNTHETIC) != 0) {
114 			return;
115 		}
116 		final ClassVisitor visitor = createAnalyzingVisitor(classId,
117 				reader.getClassName());
118 		reader.accept(visitor, 0);
119 	}
120 
121 	/**
122 	 * Analyzes the class definition from a given in-memory buffer.
123 	 *
124 	 * @param buffer
125 	 *            class definitions
126 	 * @param location
127 	 *            a location description used for exception messages
128 	 * @throws IOException
129 	 *             if the class can't be analyzed
130 	 */
analyzeClass(final byte[] buffer, final String location)131 	public void analyzeClass(final byte[] buffer, final String location)
132 			throws IOException {
133 		try {
134 			analyzeClass(buffer);
135 		} catch (final RuntimeException cause) {
136 			throw analyzerError(location, cause);
137 		}
138 	}
139 
140 	/**
141 	 * Analyzes the class definition from a given input stream. The provided
142 	 * {@link InputStream} is not closed by this method.
143 	 *
144 	 * @param input
145 	 *            stream to read class definition from
146 	 * @param location
147 	 *            a location description used for exception messages
148 	 * @throws IOException
149 	 *             if the stream can't be read or the class can't be analyzed
150 	 */
analyzeClass(final InputStream input, final String location)151 	public void analyzeClass(final InputStream input, final String location)
152 			throws IOException {
153 		final byte[] buffer;
154 		try {
155 			buffer = InputStreams.readFully(input);
156 		} catch (final IOException e) {
157 			throw analyzerError(location, e);
158 		}
159 		analyzeClass(buffer, location);
160 	}
161 
analyzerError(final String location, final Exception cause)162 	private IOException analyzerError(final String location,
163 			final Exception cause) {
164 		final IOException ex = new IOException(
165 				String.format("Error while analyzing %s.", location));
166 		ex.initCause(cause);
167 		return ex;
168 	}
169 
170 	/**
171 	 * Analyzes all classes found in the given input stream. The input stream
172 	 * may either represent a single class file, a ZIP archive, a Pack200
173 	 * archive or a gzip stream that is searched recursively for class files.
174 	 * All other content types are ignored. The provided {@link InputStream} is
175 	 * not closed by this method.
176 	 *
177 	 * @param input
178 	 *            input data
179 	 * @param location
180 	 *            a location description used for exception messages
181 	 * @return number of class files found
182 	 * @throws IOException
183 	 *             if the stream can't be read or a class can't be analyzed
184 	 */
analyzeAll(final InputStream input, final String location)185 	public int analyzeAll(final InputStream input, final String location)
186 			throws IOException {
187 		final ContentTypeDetector detector;
188 		try {
189 			detector = new ContentTypeDetector(input);
190 		} catch (final IOException e) {
191 			throw analyzerError(location, e);
192 		}
193 		switch (detector.getType()) {
194 		case ContentTypeDetector.CLASSFILE:
195 			analyzeClass(detector.getInputStream(), location);
196 			return 1;
197 		case ContentTypeDetector.ZIPFILE:
198 			return analyzeZip(detector.getInputStream(), location);
199 		case ContentTypeDetector.GZFILE:
200 			return analyzeGzip(detector.getInputStream(), location);
201 		case ContentTypeDetector.PACK200FILE:
202 			return analyzePack200(detector.getInputStream(), location);
203 		default:
204 			return 0;
205 		}
206 	}
207 
208 	/**
209 	 * Analyzes all class files contained in the given file or folder. Class
210 	 * files as well as ZIP files are considered. Folders are searched
211 	 * recursively.
212 	 *
213 	 * @param file
214 	 *            file or folder to look for class files
215 	 * @return number of class files found
216 	 * @throws IOException
217 	 *             if the file can't be read or a class can't be analyzed
218 	 */
analyzeAll(final File file)219 	public int analyzeAll(final File file) throws IOException {
220 		int count = 0;
221 		if (file.isDirectory()) {
222 			for (final File f : file.listFiles()) {
223 				count += analyzeAll(f);
224 			}
225 		} else {
226 			final InputStream in = new FileInputStream(file);
227 			try {
228 				count += analyzeAll(in, file.getPath());
229 			} finally {
230 				in.close();
231 			}
232 		}
233 		return count;
234 	}
235 
236 	/**
237 	 * Analyzes all classes from the given class path. Directories containing
238 	 * class files as well as archive files are considered.
239 	 *
240 	 * @param path
241 	 *            path definition
242 	 * @param basedir
243 	 *            optional base directory, if <code>null</code> the current
244 	 *            working directory is used as the base for relative path
245 	 *            entries
246 	 * @return number of class files found
247 	 * @throws IOException
248 	 *             if a file can't be read or a class can't be analyzed
249 	 */
analyzeAll(final String path, final File basedir)250 	public int analyzeAll(final String path, final File basedir)
251 			throws IOException {
252 		int count = 0;
253 		final StringTokenizer st = new StringTokenizer(path,
254 				File.pathSeparator);
255 		while (st.hasMoreTokens()) {
256 			count += analyzeAll(new File(basedir, st.nextToken()));
257 		}
258 		return count;
259 	}
260 
analyzeZip(final InputStream input, final String location)261 	private int analyzeZip(final InputStream input, final String location)
262 			throws IOException {
263 		final ZipInputStream zip = new ZipInputStream(input);
264 		ZipEntry entry;
265 		int count = 0;
266 		while ((entry = nextEntry(zip, location)) != null) {
267 			count += analyzeAll(zip, location + "@" + entry.getName());
268 		}
269 		return count;
270 	}
271 
nextEntry(final ZipInputStream input, final String location)272 	private ZipEntry nextEntry(final ZipInputStream input,
273 			final String location) throws IOException {
274 		try {
275 			return input.getNextEntry();
276 		} catch (final IOException e) {
277 			throw analyzerError(location, e);
278 		}
279 	}
280 
analyzeGzip(final InputStream input, final String location)281 	private int analyzeGzip(final InputStream input, final String location)
282 			throws IOException {
283 		GZIPInputStream gzipInputStream;
284 		try {
285 			gzipInputStream = new GZIPInputStream(input);
286 		} catch (final IOException e) {
287 			throw analyzerError(location, e);
288 		}
289 		return analyzeAll(gzipInputStream, location);
290 	}
291 
analyzePack200(final InputStream input, final String location)292 	private int analyzePack200(final InputStream input, final String location)
293 			throws IOException {
294 		InputStream unpackedInput;
295 		try {
296 			unpackedInput = Pack200Streams.unpack(input);
297 		} catch (final IOException e) {
298 			throw analyzerError(location, e);
299 		}
300 		return analyzeAll(unpackedInput, location);
301 	}
302 
303 }
304