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.internal.startup;
6   
7   import static mockit.internal.startup.ClassLoadingBridgeFields.createSyntheticFieldsInJREClassToHoldClassLoadingBridges;
8   
9   import edu.umd.cs.findbugs.annotations.NonNull;
10  import edu.umd.cs.findbugs.annotations.Nullable;
11  
12  import java.lang.instrument.ClassDefinition;
13  import java.lang.instrument.Instrumentation;
14  import java.lang.instrument.UnmodifiableClassException;
15  
16  import mockit.internal.ClassIdentification;
17  import mockit.internal.expectations.transformation.ExpectationsTransformer;
18  import mockit.internal.state.CachedClassfiles;
19  
20  /**
21   * This is the "agent class" that initializes the JMockit "Java agent", provided the JVM is initialized with
22   *
23   * <pre>{@code -javaagent:&lt;properPathTo>/jmockit-1-x.jar }</pre>
24   *
25   * .
26   *
27   * @see #premain(String, Instrumentation)
28   */
29  public final class Startup {
30      @Nullable
31      private static Instrumentation instrumentation;
32      public static boolean initializing;
33  
34      private Startup() {
35      }
36  
37      /**
38       * User-specified fakes will applied at this time, if the "fakes" system property is set to the fully qualified
39       * class names.
40       *
41       * @param agentArgs
42       *            if "coverage", the coverage tool is activated
43       * @param inst
44       *            the instrumentation service provided by the JVM
45       */
46      public static void premain(@Nullable String agentArgs, @NonNull Instrumentation inst) {
47          createSyntheticFieldsInJREClassToHoldClassLoadingBridges(inst);
48  
49          instrumentation = inst;
50          inst.addTransformer(CachedClassfiles.INSTANCE, true);
51  
52          initializing = true;
53          try {
54              JMockitInitialization.initialize(inst, "coverage".equals(agentArgs));
55          } finally {
56              initializing = false;
57          }
58  
59          inst.addTransformer(new ExpectationsTransformer());
60      }
61  
62      @NonNull
63      @SuppressWarnings("ConstantConditions")
64      public static Instrumentation instrumentation() {
65          return instrumentation;
66      }
67  
68      public static void verifyInitialization() {
69          if (instrumentation == null) {
70              throw new IllegalStateException(
71                      "JMockit didn't get initialized; please check the -javaagent JVM initialization parameter was used");
72          }
73      }
74  
75      @SuppressWarnings("ConstantConditions")
76      public static void retransformClass(@NonNull Class<?> aClass) {
77          try {
78              instrumentation.retransformClasses(aClass);
79          } catch (UnmodifiableClassException ignore) {
80          }
81      }
82  
83      public static void redefineMethods(@NonNull ClassIdentification classToRedefine,
84              @NonNull byte[] modifiedClassfile) {
85          Class<?> loadedClass = classToRedefine.getLoadedClass();
86          redefineMethods(loadedClass, modifiedClassfile);
87      }
88  
89      public static void redefineMethods(@NonNull Class<?> classToRedefine, @NonNull byte[] modifiedClassfile) {
90          redefineMethods(new ClassDefinition(classToRedefine, modifiedClassfile));
91      }
92  
93      public static void redefineMethods(@NonNull ClassDefinition... classDefs) {
94          try {
95              // noinspection ConstantConditions
96              instrumentation.redefineClasses(classDefs);
97          } catch (ClassNotFoundException | UnmodifiableClassException e) {
98              throw new RuntimeException(e); // should never happen
99          } catch (InternalError ignore) {
100             // If a class to be redefined hasn't been loaded yet, the JVM may get a NoClassDefFoundError during
101             // redefinition. Unfortunately, it then throws a plain InternalError instead.
102             for (ClassDefinition classDef : classDefs) {
103                 detectMissingDependenciesIfAny(classDef.getDefinitionClass());
104             }
105 
106             // If the above didn't throw upon detecting a NoClassDefFoundError, then ignore the original error and
107             // continue, in order to prevent secondary failures.
108         }
109     }
110 
111     private static void detectMissingDependenciesIfAny(@NonNull Class<?> mockedClass) {
112         try {
113             Class.forName(mockedClass.getName(), false, mockedClass.getClassLoader());
114         } catch (NoClassDefFoundError e) {
115             throw new RuntimeException("Unable to mock " + mockedClass + " due to a missing dependency", e);
116         } catch (ClassNotFoundException ignore) {
117             // Shouldn't happen since the mocked class would already have been found in the classpath.
118         }
119     }
120 
121     @Nullable
122     public static Class<?> getClassIfLoaded(@NonNull String classDescOrName) {
123         String className = classDescOrName.replace('/', '.');
124         @SuppressWarnings("ConstantConditions")
125         Class<?>[] loadedClasses = instrumentation.getAllLoadedClasses();
126 
127         for (Class<?> aClass : loadedClasses) {
128             if (aClass.getName().equals(className)) {
129                 return aClass;
130             }
131         }
132 
133         return null;
134     }
135 }