1
2
3
4
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
23
24
25
26
27
28
29
30 public final class Startup {
31 @Nullable
32 private static Instrumentation instrumentation;
33 public static boolean initializing;
34
35 private static final String UNMOCKABLE_CLASS_SUGGESTION = "This typically occurs with classes from restricted JDK"
36 + " modules (such as java.net, java.nio) in JDK 9+. Consider wrapping the class in a testable abstraction"
37 + " or interface that can be mocked instead. For example, define an interface for the operations you need"
38 + " to test and inject it into the class under test.";
39
40 private Startup() {
41 }
42
43
44
45
46
47
48
49
50
51
52 public static void premain(@Nullable String agentArgs, @NonNull Instrumentation inst) {
53 createSyntheticFieldsInJREClassToHoldClassLoadingBridges(inst);
54
55 instrumentation = inst;
56 inst.addTransformer(CachedClassfiles.INSTANCE, true);
57
58 initializing = true;
59 try {
60 JMockitInitialization.initialize(inst, "coverage".equals(agentArgs));
61 } finally {
62 initializing = false;
63 }
64
65 inst.addTransformer(new ExpectationsTransformer());
66 }
67
68 @NonNull
69 @SuppressWarnings("ConstantConditions")
70 public static Instrumentation instrumentation() {
71 return instrumentation;
72 }
73
74 public static void verifyInitialization() {
75 if (instrumentation == null) {
76 throw new IllegalStateException(
77 "JMockit didn't get initialized; please check the -javaagent JVM initialization parameter was used");
78 }
79 }
80
81 @SuppressWarnings("ConstantConditions")
82 public static void retransformClass(@NonNull Class<?> aClass) {
83 try {
84 instrumentation.retransformClasses(aClass);
85 } catch (UnmodifiableClassException ignore) {
86 }
87 }
88
89 public static void redefineMethods(@NonNull ClassIdentification classToRedefine,
90 @NonNull byte[] modifiedClassfile) {
91 Class<?> loadedClass = classToRedefine.getLoadedClass();
92 redefineMethods(loadedClass, modifiedClassfile);
93 }
94
95 public static void redefineMethods(@NonNull Class<?> classToRedefine, @NonNull byte[] modifiedClassfile) {
96 redefineMethods(new ClassDefinition(classToRedefine, modifiedClassfile));
97 }
98
99 public static void redefineMethods(@NonNull ClassDefinition... classDefs) {
100 for (ClassDefinition classDef : classDefs) {
101 checkClassIsModifiable(classDef.getDefinitionClass());
102 }
103
104 try {
105
106 instrumentation.redefineClasses(classDefs);
107 } catch (ClassNotFoundException e) {
108 throw new RuntimeException(e);
109 } catch (UnmodifiableClassException e) {
110
111
112 throw new IllegalArgumentException(buildUnmockableErrorMessage(classDefs), e);
113 } catch (InternalError ignore) {
114
115
116 for (ClassDefinition classDef : classDefs) {
117 detectMissingDependenciesIfAny(classDef.getDefinitionClass());
118 }
119
120
121
122 }
123 }
124
125 private static void checkClassIsModifiable(@NonNull Class<?> classToRedefine) {
126
127 if (!instrumentation.isModifiableClass(classToRedefine)) {
128 throw new IllegalArgumentException("Class " + classToRedefine.getName()
129 + " cannot be mocked/faked because the JVM does not allow it to be modified. "
130 + UNMOCKABLE_CLASS_SUGGESTION);
131 }
132 }
133
134 @NonNull
135 private static String buildUnmockableErrorMessage(@NonNull ClassDefinition[] classDefs) {
136 StringBuilder sb = new StringBuilder("The JVM prevented modification of class(es):");
137 for (ClassDefinition classDef : classDefs) {
138 sb.append(' ').append(classDef.getDefinitionClass().getName());
139 }
140 sb.append(". ").append(UNMOCKABLE_CLASS_SUGGESTION);
141 return sb.toString();
142 }
143
144 private static void detectMissingDependenciesIfAny(@NonNull Class<?> mockedClass) {
145 try {
146 Class.forName(mockedClass.getName(), false, mockedClass.getClassLoader());
147 } catch (NoClassDefFoundError e) {
148 throw new RuntimeException("Unable to mock " + mockedClass + " due to a missing dependency", e);
149 } catch (ClassNotFoundException ignore) {
150
151 }
152 }
153
154 @Nullable
155 public static Class<?> getClassIfLoaded(@NonNull String classDescOrName) {
156 String className = classDescOrName.replace('/', '.');
157 @SuppressWarnings("ConstantConditions")
158 Class<?>[] loadedClasses = instrumentation.getAllLoadedClasses();
159
160 for (Class<?> aClass : loadedClasses) {
161 if (aClass.getName().equals(className)) {
162 return aClass;
163 }
164 }
165
166 return null;
167 }
168 }