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