View Javadoc
1   /*
2    * Copyright (c) 2006 JMockit developers
3    * This file is subject to the terms of the MIT license (see LICENSE.txt).
4    */
5   package mockit.coverage.modification;
6   
7   import edu.umd.cs.findbugs.annotations.NonNull;
8   import edu.umd.cs.findbugs.annotations.Nullable;
9   
10  import java.security.ProtectionDomain;
11  import java.util.ArrayList;
12  import java.util.HashSet;
13  import java.util.List;
14  import java.util.Set;
15  
16  import mockit.asm.classes.ClassReader;
17  
18  public final class ClassModification {
19      @NonNull
20      private final Set<String> modifiedClasses;
21      @NonNull
22      final List<ProtectionDomain> protectionDomainsWithUniqueLocations;
23      @NonNull
24      private final ClassSelection classSelection;
25  
26      public ClassModification() {
27          modifiedClasses = new HashSet<>();
28          protectionDomainsWithUniqueLocations = new ArrayList<>();
29          classSelection = new ClassSelection();
30      }
31  
32      public boolean shouldConsiderClassesNotLoaded() {
33          return !classSelection.loadedOnly;
34      }
35  
36      boolean isToBeConsideredForCoverage(@NonNull String className, @NonNull ProtectionDomain protectionDomain) {
37          return !modifiedClasses.contains(className) && classSelection.isSelected(className, protectionDomain);
38      }
39  
40      @Nullable
41      public byte[] modifyClass(@NonNull String className, @NonNull ProtectionDomain protectionDomain,
42              @NonNull byte[] originalClassfile) {
43          if (isToBeConsideredForCoverage(className, protectionDomain)) {
44              try {
45                  byte[] modifiedClassfile = modifyClassForCoverage(className, originalClassfile);
46                  registerModifiedClass(className, protectionDomain);
47                  return modifiedClassfile;
48              } catch (VisitInterruptedException ignore) {
49                  // Ignore the class if the modification was refused for some reason.
50              } catch (RuntimeException | AssertionError | ClassCircularityError e) {
51                  e.printStackTrace();
52              }
53          }
54  
55          return null;
56      }
57  
58      @NonNull
59      private static byte[] modifyClassForCoverage(@NonNull String className, @NonNull byte[] classBytecode) {
60          byte[] modifiedBytecode = CoverageModifier.recoverModifiedByteCodeIfAvailable(className);
61  
62          if (modifiedBytecode != null) {
63              return modifiedBytecode;
64          }
65  
66          ClassReader cr = new ClassReader(classBytecode);
67          CoverageModifier modifier = new CoverageModifier(cr);
68          cr.accept(modifier);
69          return modifier.toByteArray();
70      }
71  
72      private void registerModifiedClass(@NonNull String className, @NonNull ProtectionDomain pd) {
73          modifiedClasses.add(className);
74  
75          if (pd.getClassLoader() != null && pd.getCodeSource() != null && pd.getCodeSource().getLocation() != null) {
76              addProtectionDomainIfHasUniqueNewPath(pd);
77          }
78      }
79  
80      private void addProtectionDomainIfHasUniqueNewPath(@NonNull ProtectionDomain newPD) {
81          String newPath = newPD.getCodeSource().getLocation().getPath();
82  
83          for (int i = protectionDomainsWithUniqueLocations.size() - 1; i >= 0; i--) {
84              ProtectionDomain previousPD = protectionDomainsWithUniqueLocations.get(i);
85              String previousPath = previousPD.getCodeSource().getLocation().getPath();
86  
87              if (previousPath.startsWith(newPath)) {
88                  return;
89              }
90              if (newPath.startsWith(previousPath)) {
91                  protectionDomainsWithUniqueLocations.set(i, newPD);
92                  return;
93              }
94          }
95  
96          protectionDomainsWithUniqueLocations.add(newPD);
97      }
98  }