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 is a Java toolkit for automated developer testing. It contains mocking/faking APIs and a code coverage tool, supporting both JUnit and TestNG. The mocking APIs allow all kinds of Java code, without testability restrictions, to be tested in isolation from selected dependencies.

There is a newer version: 1.49
Show newest version
/*
 * Copyright (c) 2006 JMockit developers
 * This file is subject to the terms of the MIT license (see LICENSE.txt).
 */
package mockit.coverage.modification;

import java.util.*;
import javax.annotation.*;

import mockit.asm.annotations.*;
import mockit.asm.classes.*;
import mockit.asm.controlFlow.*;
import mockit.asm.fields.*;
import mockit.asm.methods.*;
import mockit.coverage.data.*;
import mockit.coverage.lines.*;
import mockit.internal.*;
import static mockit.asm.jvmConstants.Access.*;
import static mockit.asm.jvmConstants.Opcodes.*;

final class CoverageModifier extends WrappingClassVisitor
{
   private static final Map INNER_CLASS_MODIFIERS = new HashMap<>();
   private static final int FIELD_MODIFIERS_TO_IGNORE = FINAL + SYNTHETIC;

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

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

   @Nullable private String internalClassName;
   @Nullable private String simpleClassName;
   @Nonnull private String sourceFileName;
   @Nullable private FileCoverageData fileData;
   private final boolean forInnerClass;
   private boolean forEnumClass;
   @Nullable private String kindOfTopLevelType;
   private int currentLine;

   CoverageModifier(@Nonnull ClassReader cr) { this(cr, false); }

   private CoverageModifier(@Nonnull ClassReader cr, boolean forInnerClass) {
      super(new ClassWriter(cr));
      sourceFileName = "";
      this.forInnerClass = forInnerClass;
   }

   private CoverageModifier(@Nonnull ClassReader cr, @Nonnull 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, @Nonnull String name, @Nonnull ClassInfo additionalInfo) {
      if ((access & SYNTHETIC) != 0) {
         throw new VisitInterruptedException();
      }

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

      if (!nestedType && kindOfTopLevelType == null) {
         //noinspection ConstantConditions
         kindOfTopLevelType = getKindOfJavaType(access, additionalInfo.superName);
      }

      forEnumClass = (access & ENUM) != 0;

      String sourceFileDebugName = getSourceFileDebugName(additionalInfo);

      if (!forInnerClass) {
         extractClassAndSourceFileName(name);

         boolean cannotModify = (access & ANNOTATION) != 0;

         if (cannotModify) {
            throw VisitInterruptedException.INSTANCE;
         }

         registerAsInnerClassModifierIfApplicable(access, name, nestedType);
         createFileData(sourceFileDebugName);
      }

      cw.visit(version, access, name, additionalInfo);
   }

   @Nonnull
   private static String getKindOfJavaType(int typeModifiers, @Nonnull String superName) {
      if ((typeModifiers & ANNOTATION) != 0) return "annotation";
      if ((typeModifiers & INTERFACE) != 0) return "interface";
      if ((typeModifiers & ENUM) != 0) return "enum";
      if ((typeModifiers & ABSTRACT) != 0) return "abstractClass";
      if (superName.endsWith("Exception") || superName.endsWith("Error")) return "exception";
      return "class";
   }

   @Nonnull
   private static String getSourceFileDebugName(@Nonnull ClassInfo additionalInfo) {
      String sourceFileDebugName = additionalInfo.sourceFileName;

      if (sourceFileDebugName == null || !sourceFileDebugName.endsWith(".java")) {
         throw VisitInterruptedException.INSTANCE;
      }

      return sourceFileDebugName;
   }

   private void extractClassAndSourceFileName(@Nonnull String className) {
      internalClassName = className;
      int p = className.lastIndexOf('/');

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

   private void registerAsInnerClassModifierIfApplicable(int access, @Nonnull String name, boolean nestedType) {
      if (!forEnumClass && (access & SUPER) != 0 && nestedType) {
         INNER_CLASS_MODIFIERS.put(name.replace('/', '.'), this);
      }
   }

   private void createFileData(@Nonnull String sourceFileDebugName) {
      sourceFileName += sourceFileDebugName;
      fileData = CoverageData.instance().getOrAddFile(sourceFileName, kindOfTopLevelType);
   }

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

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

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

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

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

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

   private static boolean isSyntheticOrEnumClass(int access) {
      return (access & SYNTHETIC) != 0 || access == STATIC + ENUM;
   }

   private boolean isNestedInsideClassBeingModified(@Nonnull String internalName, @Nullable String outerName) {
      String className = outerName == null ? internalName : outerName;
      int p = className.indexOf('$');
      String outerClassName = p < 0 ? className : className.substring(0, p);

      return outerClassName.equals(internalClassName);
   }

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

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

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

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

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

         return new ConstructorModifier(mw);
      }

      return new MethodModifier(mw);
   }

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

      @Nonnull protected final List




© 2015 - 2024 Weber Informatics LLC | Privacy Policy