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