All Downloads are FREE. Search and download functionalities are using the official Maven repository.

mockit.coverage.modification.CoverageModifier Maven / Gradle / Ivy

Go to download

JMockit Coverage is a code coverage tool with several metrics (line, path, data) capable of generating HTML reports. It is designed with ease of use in mind, avoiding the need for complex configuration. Instead, smart (but overridable) defaults are employed, such as the selection of which classes to consider for coverage, and where to find sources files for report generation.

The newest version!
/*
 * Copyright (c) 2006-2013 Rogério Liesenfeld
 * This file is subject to the terms of the MIT license (see LICENSE.txt).
 */
package mockit.coverage.modification;

import java.io.*;
import java.util.*;

import org.jetbrains.annotations.*;

import static mockit.external.asm4.Opcodes.*;

import mockit.coverage.*;
import mockit.coverage.data.*;
import mockit.coverage.lines.*;
import mockit.coverage.paths.*;
import mockit.external.asm4.*;

final class CoverageModifier extends ClassVisitor
{
   private static final Map INNER_CLASS_MODIFIERS = new HashMap();
   private static final int FIELD_MODIFIERS_TO_IGNORE = ACC_FINAL + ACC_SYNTHETIC;
   private static final int MAX_CONDITIONS = Integer.getInteger("jmockit-coverage-maxConditions", 10);

   @Nullable static byte[] recoverModifiedByteCodeIfAvailable(@NotNull String innerClassName)
   {
      CoverageModifier modifier = INNER_CLASS_MODIFIERS.remove(innerClassName);
      return modifier == null ? null : modifier.toByteArray();
   }

   @Nullable static ClassReader createClassReader(@NotNull Class aClass)
   {
      return createClassReader(aClass.getClassLoader(), aClass.getName().replace('.', '/'));
   }

   @Nullable private static ClassReader createClassReader(@NotNull ClassLoader cl, @NotNull String internalClassName)
   {
      InputStream classFile = cl.getResourceAsStream(internalClassName + ".class");

      if (classFile == null) {
         // Ignore the class if the ".class" file wasn't located.
         return null;
      }

      try { return new ClassReader(classFile); } catch (IOException ignore) { return null; }
   }

   @NotNull private final ClassWriter cw;
   @Nullable private String internalClassName;
   @Nullable private String simpleClassName;
   @NotNull private String sourceFileName;
   @Nullable private FileCoverageData fileData;
   private boolean cannotModify;
   private final boolean forInnerClass;
   private boolean forEnumClass;
   @Nullable private String kindOfTopLevelType;
   private int currentLine;

   CoverageModifier(@NotNull ClassReader cr)
   {
      this(cr, false);
      sourceFileName = "";
   }

   private CoverageModifier(@NotNull ClassReader cr, boolean forInnerClass)
   {
      super(new ClassWriter(cr, ClassWriter.COMPUTE_MAXS));
      //noinspection ConstantConditions
      cw = (ClassWriter) cv;
      this.forInnerClass = forInnerClass;
   }

   private CoverageModifier(@NotNull ClassReader cr, @NotNull CoverageModifier other, @Nullable String simpleClassName)
   {
      this(cr, true);
      sourceFileName = other.sourceFileName;
      fileData = other.fileData;
      internalClassName = other.internalClassName;
      this.simpleClassName = simpleClassName;
   }

   @Override
   public void visit(
      int version, int access, @NotNull String name, @Nullable String signature, String superName,
      @Nullable String[] interfaces)
   {
      if ((access & ACC_SYNTHETIC) != 0) {
         throw new VisitInterruptedException();
      }

      boolean nestedType = name.indexOf('$') > 0;

      if (!nestedType && kindOfTopLevelType == null) {
         kindOfTopLevelType = getKindOfJavaType(access, superName);
      }

      forEnumClass = (access & ACC_ENUM) != 0;

      if (!forInnerClass) {
         internalClassName = name;
         int p = name.lastIndexOf('/');

         if (p < 0) {
            simpleClassName = name;
            sourceFileName = "";
         }
         else {
            simpleClassName = name.substring(p + 1);
            sourceFileName = name.substring(0, p + 1);
         }

         cannotModify = (access & ACC_ANNOTATION) != 0;

         if (!forEnumClass && (access & ACC_SUPER) != 0 && nestedType) {
            INNER_CLASS_MODIFIERS.put(name.replace('/', '.'), this);
         }
      }

      // A VerifyError can occur with Java 7, related to stack map frames. ASM has a bug affecting "COMPUTE_FRAMES",
      // so the only solution was to "downgrade" the bytecode to Java 6.
      int finalVersion = (version & 0xFFFF) == V1_7 ? V1_6 : version;
      cw.visit(finalVersion, access, name, signature, superName, interfaces);
   }

   @NotNull private String getKindOfJavaType(int typeModifiers, @NotNull String superName)
   {
      if ((typeModifiers & ACC_ANNOTATION) != 0) return "annotation";
      else if ((typeModifiers & ACC_INTERFACE) != 0) return "interface";
      else if ((typeModifiers & ACC_ENUM) != 0) return "enum";
      else if ((typeModifiers & ACC_ABSTRACT) != 0) return "abstractClass";
      else if (superName.endsWith("Exception") || superName.endsWith("Error")) return "exception";
      return "class";
   }

   @Override
   public void visitSource(@Nullable String file, @Nullable String debug)
   {
      if (!forInnerClass) {
         if (cannotModify) {
            throw VisitInterruptedException.INSTANCE;
         }

         sourceFileName += file;
         fileData = CoverageData.instance().getOrAddFile(sourceFileName, kindOfTopLevelType);
      }

      cw.visitSource(file, debug);
   }

   @Override
   public void visitInnerClass(
      @NotNull String internalName, @Nullable String outerName, @Nullable String innerName, int access)
   {
      cw.visitInnerClass(internalName, outerName, innerName, access);

      if (forInnerClass || isSyntheticOrEnumClass(access) || !isNestedInsideClassBeingModified(outerName)) {
         return;
      }

      String innerClassName = internalName.replace('/', '.');

      if (INNER_CLASS_MODIFIERS.containsKey(innerClassName)) {
         return;
      }

      ClassReader innerCR = createClassReader(CoverageModifier.class.getClassLoader(), internalName);

      if (innerCR != null) {
         CoverageModifier innerClassModifier = new CoverageModifier(innerCR, this, innerName);
         innerCR.accept(innerClassModifier, 0);
         INNER_CLASS_MODIFIERS.put(innerClassName, innerClassModifier);
      }
   }

   private boolean isSyntheticOrEnumClass(int access)
   {
      return (access & ACC_SYNTHETIC) != 0 || access == ACC_STATIC + ACC_ENUM;
   }

   private boolean isNestedInsideClassBeingModified(@Nullable String outerName)
   {
      if (outerName == null) {
         return false;
      }

      int p = outerName.indexOf('$');
      String outerClassName = p < 0 ? outerName : outerName.substring(0, p);

      return outerClassName.equals(internalClassName);
   }

   @Override
   public FieldVisitor visitField(
      int access, @NotNull String name, @NotNull String desc, @Nullable String signature, @Nullable Object value)
   {
      if (
         fileData != null && simpleClassName != null &&
         (access & FIELD_MODIFIERS_TO_IGNORE) == 0 && Metrics.DataCoverage.isActive()
      ) {
         fileData.dataCoverageInfo.addField(simpleClassName, name, (access & ACC_STATIC) != 0);
      }

      return cw.visitField(access, name, desc, signature, value);
   }

   @Override
   public MethodVisitor visitMethod(
      int access, @NotNull String name, @NotNull String desc, @Nullable String signature, @Nullable String[] exceptions)
   {
      MethodWriter mw = cw.visitMethod(access, name, desc, signature, exceptions);

      if (fileData == null || (access & ACC_SYNTHETIC) != 0) {
         return mw;
      }

      boolean withPathOrDataCoverage = Metrics.PathCoverage.isActive() || Metrics.DataCoverage.isActive();

      if (name.charAt(0) == '<') {
         if (name.charAt(1) == 'c') {
            return forEnumClass ? mw : new StaticBlockModifier(mw);
         }

         if (withPathOrDataCoverage) {
            return new ConstructorModifier(mw);
         }
      }

      return withPathOrDataCoverage ? new MethodModifier(mw) : new BaseMethodModifier(mw);
   }

   private class BaseMethodModifier extends MethodVisitor
   {
      static final String DATA_RECORDING_CLASS = "mockit/coverage/TestRun";

      @NotNull final MethodWriter mw;
      @NotNull final List




© 2015 - 2025 Weber Informatics LLC | Privacy Policy