ClassAnalyzer.java

  1. /*******************************************************************************
  2.  * Copyright (c) 2009, 2025 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.internal.analysis;

  14. import java.util.Arrays;
  15. import java.util.HashMap;
  16. import java.util.HashSet;
  17. import java.util.Map;
  18. import java.util.Set;

  19. import org.jacoco.core.internal.analysis.filter.Filters;
  20. import org.jacoco.core.internal.analysis.filter.IFilter;
  21. import org.jacoco.core.internal.analysis.filter.IFilterContext;
  22. import org.jacoco.core.internal.analysis.filter.KotlinSMAP;
  23. import org.jacoco.core.internal.flow.ClassProbesVisitor;
  24. import org.jacoco.core.internal.flow.MethodProbesVisitor;
  25. import org.jacoco.core.internal.instr.InstrSupport;
  26. import org.objectweb.asm.AnnotationVisitor;
  27. import org.objectweb.asm.Attribute;
  28. import org.objectweb.asm.FieldVisitor;
  29. import org.objectweb.asm.MethodVisitor;
  30. import org.objectweb.asm.tree.AbstractInsnNode;
  31. import org.objectweb.asm.tree.MethodNode;

  32. /**
  33.  * Analyzes the structure of a class.
  34.  */
  35. public class ClassAnalyzer extends ClassProbesVisitor
  36.         implements IFilterContext {

  37.     private final ClassCoverageImpl coverage;
  38.     private final boolean[] probes;
  39.     private final StringPool stringPool;

  40.     private final Set<String> classAnnotations = new HashSet<String>();

  41.     private final Set<String> classAttributes = new HashSet<String>();

  42.     private String sourceDebugExtension;
  43.     private KotlinSMAP smap;
  44.     private final HashMap<String, SourceNodeImpl> fragments = new HashMap<String, SourceNodeImpl>();

  45.     private final IFilter filter;

  46.     /**
  47.      * Creates a new analyzer that builds coverage data for a class.
  48.      *
  49.      * @param coverage
  50.      *            coverage node for the analyzed class data
  51.      * @param probes
  52.      *            execution data for this class or <code>null</code>
  53.      * @param stringPool
  54.      *            shared pool to minimize the number of {@link String} instances
  55.      */
  56.     public ClassAnalyzer(final ClassCoverageImpl coverage,
  57.             final boolean[] probes, final StringPool stringPool) {
  58.         this.coverage = coverage;
  59.         this.probes = probes;
  60.         this.stringPool = stringPool;
  61.         this.filter = Filters.all();
  62.     }

  63.     @Override
  64.     public void visit(final int version, final int access, final String name,
  65.             final String signature, final String superName,
  66.             final String[] interfaces) {
  67.         coverage.setSignature(stringPool.get(signature));
  68.         coverage.setSuperName(stringPool.get(superName));
  69.         coverage.setInterfaces(stringPool.get(interfaces));
  70.     }

  71.     @Override
  72.     public AnnotationVisitor visitAnnotation(final String desc,
  73.             final boolean visible) {
  74.         classAnnotations.add(desc);
  75.         return super.visitAnnotation(desc, visible);
  76.     }

  77.     @Override
  78.     public void visitAttribute(final Attribute attribute) {
  79.         classAttributes.add(attribute.type);
  80.     }

  81.     @Override
  82.     public void visitSource(final String source, final String debug) {
  83.         coverage.setSourceFileName(stringPool.get(source));
  84.         sourceDebugExtension = debug;
  85.     }

  86.     @Override
  87.     public MethodProbesVisitor visitMethod(final int access, final String name,
  88.             final String desc, final String signature,
  89.             final String[] exceptions) {

  90.         InstrSupport.assertNotInstrumented(name, coverage.getName());

  91.         final InstructionsBuilder builder = new InstructionsBuilder(probes);

  92.         return new MethodAnalyzer(builder) {

  93.             @Override
  94.             public void accept(final MethodNode methodNode,
  95.                     final MethodVisitor methodVisitor) {
  96.                 super.accept(methodNode, methodVisitor);
  97.                 addMethodCoverage(stringPool.get(name), stringPool.get(desc),
  98.                         stringPool.get(signature), builder, methodNode);
  99.             }
  100.         };
  101.     }

  102.     private void addMethodCoverage(final String name, final String desc,
  103.             final String signature, final InstructionsBuilder icc,
  104.             final MethodNode methodNode) {

  105.         final Map<AbstractInsnNode, Instruction> instructions = icc
  106.                 .getInstructions();
  107.         calculateFragments(instructions);

  108.         final MethodCoverageCalculator mcc = new MethodCoverageCalculator(
  109.                 instructions);
  110.         filter.filter(methodNode, this, mcc);

  111.         final MethodCoverageImpl mc = new MethodCoverageImpl(name, desc,
  112.                 signature);
  113.         mcc.calculate(mc);

  114.         if (mc.containsCode()) {
  115.             // Only consider methods that actually contain code
  116.             coverage.addMethod(mc);
  117.         }

  118.     }

  119.     private void calculateFragments(
  120.             final Map<AbstractInsnNode, Instruction> instructions) {
  121.         if (sourceDebugExtension == null || !Filters.isKotlinClass(this)) {
  122.             return;
  123.         }
  124.         if (smap == null) {
  125.             // Note that visitSource is invoked before visitAnnotation,
  126.             // that's why parsing is done here
  127.             smap = new KotlinSMAP(getSourceFileName(), sourceDebugExtension);
  128.         }
  129.         for (final KotlinSMAP.Mapping mapping : smap.mappings()) {
  130.             if (coverage.getName().equals(mapping.inputClassName())
  131.                     && mapping.inputStartLine() == mapping.outputStartLine()) {
  132.                 continue;
  133.             }
  134.             SourceNodeImpl fragment = fragments.get(mapping.inputClassName());
  135.             if (fragment == null) {
  136.                 fragment = new SourceNodeImpl(null, mapping.inputClassName());
  137.                 fragments.put(mapping.inputClassName(), fragment);
  138.             }
  139.             final int mappingOutputEndLine = mapping.outputStartLine()
  140.                     + mapping.repeatCount() - 1;
  141.             for (Instruction instruction : instructions.values()) {
  142.                 if (mapping.outputStartLine() <= instruction.getLine()
  143.                         && instruction.getLine() <= mappingOutputEndLine) {
  144.                     final int originalLine = mapping.inputStartLine()
  145.                             + instruction.getLine() - mapping.outputStartLine();
  146.                     fragment.increment(instruction.getInstructionCounter(),
  147.                             CounterImpl.COUNTER_0_0, originalLine);
  148.                 }
  149.             }
  150.         }
  151.     }

  152.     @Override
  153.     public FieldVisitor visitField(final int access, final String name,
  154.             final String desc, final String signature, final Object value) {
  155.         InstrSupport.assertNotInstrumented(name, coverage.getName());
  156.         return super.visitField(access, name, desc, signature, value);
  157.     }

  158.     @Override
  159.     public void visitTotalProbeCount(final int count) {
  160.         // nothing to do
  161.     }

  162.     @Override
  163.     public void visitEnd() {
  164.         if (!fragments.isEmpty()) {
  165.             coverage.setFragments(Arrays
  166.                     .asList(fragments.values().toArray(new SourceNodeImpl[0])));
  167.         }
  168.     }

  169.     // IFilterContext implementation

  170.     public String getClassName() {
  171.         return coverage.getName();
  172.     }

  173.     public String getSuperClassName() {
  174.         return coverage.getSuperName();
  175.     }

  176.     public Set<String> getClassAnnotations() {
  177.         return classAnnotations;
  178.     }

  179.     public Set<String> getClassAttributes() {
  180.         return classAttributes;
  181.     }

  182.     public String getSourceFileName() {
  183.         return coverage.getSourceFileName();
  184.     }

  185.     public String getSourceDebugExtension() {
  186.         return sourceDebugExtension;
  187.     }

  188. }