/******************************************************************************* * Copyright (c) 2009, 2021 Mountainminds GmbH & Co. KG and Contributors * This program and the accompanying materials are made available under * the terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0 * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Marc R. Hoffmann - initial API and implementation * *******************************************************************************/ package org.jacoco.core.analysis; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.StringTokenizer; import java.util.zip.GZIPInputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import org.jacoco.core.data.IExecutionData; import org.jacoco.core.data.ExecutionDataStore; import org.jacoco.core.internal.ContentTypeDetector; import org.jacoco.core.internal.InputStreams; import org.jacoco.core.internal.Pack200Streams; import org.jacoco.core.internal.analysis.ClassAnalyzer; import org.jacoco.core.internal.analysis.ClassCoverageImpl; import org.jacoco.core.internal.analysis.StringPool; import org.jacoco.core.internal.data.CRC64; import org.jacoco.core.internal.flow.ClassProbesAdapter; import org.jacoco.core.internal.instr.InstrSupport; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Opcodes; /** * An {@link Analyzer} instance processes a set of Java class files and * calculates coverage data for them. For each class file the result is reported * to a given {@link ICoverageVisitor} instance. In addition the * {@link Analyzer} requires a {@link ExecutionDataStore} instance that holds * the execution data for the classes to analyze. The {@link Analyzer} offers * several methods to analyze classes from a variety of sources. */ public class Analyzer { private final ExecutionDataStore executionData; private final ICoverageVisitor coverageVisitor; private final StringPool stringPool; /** * Creates a new analyzer reporting to the given output. * * @param executionData * execution data * @param coverageVisitor * the output instance that will coverage data for every analyzed * class */ public Analyzer(final ExecutionDataStore executionData, final ICoverageVisitor coverageVisitor) { this.executionData = executionData; this.coverageVisitor = coverageVisitor; this.stringPool = new StringPool(); } /** * Creates an ASM class visitor for analysis. * * @param classid * id of the class calculated with {@link CRC64} * @param className * VM name of the class * @return ASM visitor to write class definition to */ private ClassVisitor createAnalyzingVisitor(final long classid, final String className) { // BEGIN android-change final IExecutionData data = executionData.get(classid); final boolean[] probes; final boolean noMatch; if (data == null) { probes = null; noMatch = executionData.contains(className); } else { probes = data.getProbesCopy(); noMatch = false; } // END android-change final ClassCoverageImpl coverage = new ClassCoverageImpl(className, classid, noMatch); final ClassAnalyzer analyzer = new ClassAnalyzer(coverage, probes, stringPool) { @Override public void visitEnd() { super.visitEnd(); coverageVisitor.visitCoverage(coverage); } }; return new ClassProbesAdapter(analyzer, false); } private void analyzeClass(final byte[] source) { final long classId = CRC64.classId(source); final ClassReader reader = InstrSupport.classReaderFor(source); if ((reader.getAccess() & Opcodes.ACC_MODULE) != 0) { return; } if ((reader.getAccess() & Opcodes.ACC_SYNTHETIC) != 0) { return; } final ClassVisitor visitor = createAnalyzingVisitor(classId, reader.getClassName()); reader.accept(visitor, 0); } /** * Analyzes the class definition from a given in-memory buffer. * * @param buffer * class definitions * @param location * a location description used for exception messages * @throws IOException * if the class can't be analyzed */ public void analyzeClass(final byte[] buffer, final String location) throws IOException { try { analyzeClass(buffer); } catch (final RuntimeException cause) { throw analyzerError(location, cause); } } /** * Analyzes the class definition from a given input stream. The provided * {@link InputStream} is not closed by this method. * * @param input * stream to read class definition from * @param location * a location description used for exception messages * @throws IOException * if the stream can't be read or the class can't be analyzed */ public void analyzeClass(final InputStream input, final String location) throws IOException { final byte[] buffer; try { buffer = InputStreams.readFully(input); } catch (final IOException e) { throw analyzerError(location, e); } analyzeClass(buffer, location); } private IOException analyzerError(final String location, final Exception cause) { final IOException ex = new IOException( String.format("Error while analyzing %s.", location)); ex.initCause(cause); return ex; } /** * Analyzes all classes found in the given input stream. The input stream * may either represent a single class file, a ZIP archive, a Pack200 * archive or a gzip stream that is searched recursively for class files. * All other content types are ignored. The provided {@link InputStream} is * not closed by this method. * * @param input * input data * @param location * a location description used for exception messages * @return number of class files found * @throws IOException * if the stream can't be read or a class can't be analyzed */ public int analyzeAll(final InputStream input, final String location) throws IOException { final ContentTypeDetector detector; try { detector = new ContentTypeDetector(input); } catch (final IOException e) { throw analyzerError(location, e); } switch (detector.getType()) { case ContentTypeDetector.CLASSFILE: analyzeClass(detector.getInputStream(), location); return 1; case ContentTypeDetector.ZIPFILE: return analyzeZip(detector.getInputStream(), location); case ContentTypeDetector.GZFILE: return analyzeGzip(detector.getInputStream(), location); case ContentTypeDetector.PACK200FILE: return analyzePack200(detector.getInputStream(), location); default: return 0; } } /** * Analyzes all class files contained in the given file or folder. Class * files as well as ZIP files are considered. Folders are searched * recursively. * * @param file * file or folder to look for class files * @return number of class files found * @throws IOException * if the file can't be read or a class can't be analyzed */ public int analyzeAll(final File file) throws IOException { int count = 0; if (file.isDirectory()) { for (final File f : file.listFiles()) { count += analyzeAll(f); } } else { final InputStream in = new FileInputStream(file); try { count += analyzeAll(in, file.getPath()); } finally { in.close(); } } return count; } /** * Analyzes all classes from the given class path. Directories containing * class files as well as archive files are considered. * * @param path * path definition * @param basedir * optional base directory, if null the current * working directory is used as the base for relative path * entries * @return number of class files found * @throws IOException * if a file can't be read or a class can't be analyzed */ public int analyzeAll(final String path, final File basedir) throws IOException { int count = 0; final StringTokenizer st = new StringTokenizer(path, File.pathSeparator); while (st.hasMoreTokens()) { count += analyzeAll(new File(basedir, st.nextToken())); } return count; } private int analyzeZip(final InputStream input, final String location) throws IOException { final ZipInputStream zip = new ZipInputStream(input); ZipEntry entry; int count = 0; while ((entry = nextEntry(zip, location)) != null) { count += analyzeAll(zip, location + "@" + entry.getName()); } return count; } private ZipEntry nextEntry(final ZipInputStream input, final String location) throws IOException { try { return input.getNextEntry(); } catch (final IOException e) { throw analyzerError(location, e); } } private int analyzeGzip(final InputStream input, final String location) throws IOException { GZIPInputStream gzipInputStream; try { gzipInputStream = new GZIPInputStream(input); } catch (final IOException e) { throw analyzerError(location, e); } return analyzeAll(gzipInputStream, location); } private int analyzePack200(final InputStream input, final String location) throws IOException { InputStream unpackedInput; try { unpackedInput = Pack200Streams.unpack(input); } catch (final IOException e) { throw analyzerError(location, e); } return analyzeAll(unpackedInput, location); } }