1
2
3
4
5
6 package mockit.internal.faking;
7
8 import static java.lang.reflect.Modifier.isAbstract;
9 import static java.lang.reflect.Modifier.isNative;
10 import static java.lang.reflect.Modifier.isPublic;
11 import static java.lang.reflect.Modifier.isStatic;
12
13 import static mockit.asm.jvmConstants.Opcodes.ACONST_NULL;
14 import static mockit.asm.jvmConstants.Opcodes.ALOAD;
15 import static mockit.asm.jvmConstants.Opcodes.CHECKCAST;
16 import static mockit.asm.jvmConstants.Opcodes.DUP;
17 import static mockit.asm.jvmConstants.Opcodes.DUP_X1;
18 import static mockit.asm.jvmConstants.Opcodes.IFEQ;
19 import static mockit.asm.jvmConstants.Opcodes.IFNE;
20 import static mockit.asm.jvmConstants.Opcodes.IF_ACMPEQ;
21 import static mockit.asm.jvmConstants.Opcodes.ILOAD;
22 import static mockit.asm.jvmConstants.Opcodes.INSTANCEOF;
23 import static mockit.asm.jvmConstants.Opcodes.INVOKESTATIC;
24 import static mockit.asm.jvmConstants.Opcodes.INVOKEVIRTUAL;
25 import static mockit.asm.jvmConstants.Opcodes.IRETURN;
26 import static mockit.asm.jvmConstants.Opcodes.RETURN;
27 import static mockit.asm.jvmConstants.Opcodes.SIPUSH;
28
29 import edu.umd.cs.findbugs.annotations.NonNull;
30 import edu.umd.cs.findbugs.annotations.Nullable;
31
32 import mockit.MockUp;
33 import mockit.asm.classes.ClassReader;
34 import mockit.asm.controlFlow.Label;
35 import mockit.asm.jvmConstants.Access;
36 import mockit.asm.methods.MethodVisitor;
37 import mockit.asm.types.JavaType;
38 import mockit.asm.types.ReferenceType;
39 import mockit.internal.BaseClassModifier;
40 import mockit.internal.faking.FakeMethods.FakeMethod;
41 import mockit.internal.state.TestRun;
42 import mockit.internal.util.ClassLoad;
43
44 import org.checkerframework.checker.index.qual.NonNegative;
45
46
47
48
49
50
51
52
53
54
55 final class FakedClassModifier extends BaseClassModifier {
56 private static final int ABSTRACT_OR_SYNTHETIC = Access.ABSTRACT + Access.SYNTHETIC;
57
58 @NonNull
59 private final FakeMethods fakeMethods;
60 private final boolean useClassLoadingBridgeForUpdatingFakeState;
61 @NonNull
62 private final Class<?> fakedClass;
63 private FakeMethod fakeMethod;
64 private boolean isConstructor;
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85 FakedClassModifier(@NonNull ClassReader cr, @NonNull Class<?> realClass, @NonNull MockUp<?> fake,
86 @NonNull FakeMethods fakeMethods) {
87 super(cr);
88 fakedClass = realClass;
89 this.fakeMethods = fakeMethods;
90
91 ClassLoader classLoaderOfRealClass = realClass.getClassLoader();
92 useClassLoadingBridgeForUpdatingFakeState = ClassLoad.isClassLoaderWithNoDirectAccess(classLoaderOfRealClass);
93 inferUseOfClassLoadingBridge(classLoaderOfRealClass, fake);
94 }
95
96 private void inferUseOfClassLoadingBridge(@Nullable ClassLoader classLoaderOfRealClass, @NonNull Object fake) {
97 setUseClassLoadingBridge(classLoaderOfRealClass);
98
99 if (!useClassLoadingBridge && !isPublic(fake.getClass().getModifiers())) {
100 useClassLoadingBridge = true;
101 }
102 }
103
104 @Override
105 public MethodVisitor visitMethod(int access, @NonNull String name, @NonNull String desc, @Nullable String signature,
106 @Nullable String[] exceptions) {
107 if ((access & ABSTRACT_OR_SYNTHETIC) != 0) {
108 if (isAbstract(access)) {
109
110 fakeMethods.findMethod(access, name, desc, signature);
111 }
112
113 return cw.visitMethod(access, name, desc, signature, exceptions);
114 }
115
116 isConstructor = "<init>".equals(name);
117
118 if (isConstructor && isFakedSuperclass() || !hasFake(access, name, desc, signature)) {
119 return cw.visitMethod(access, name, desc, signature, exceptions);
120 }
121
122 startModifiedMethodVersion(access, name, desc, signature, exceptions);
123
124 if (isNative(methodAccess)) {
125 generateCodeForInterceptedNativeMethod();
126 return methodAnnotationsVisitor;
127 }
128
129 return copyOriginalImplementationWithInjectedInterceptionCode();
130 }
131
132 private boolean hasFake(int access, @NonNull String name, @NonNull String desc, @Nullable String signature) {
133 String fakeName = getCorrespondingFakeName(name);
134 fakeMethod = fakeMethods.findMethod(access, fakeName, desc, signature);
135 return fakeMethod != null;
136 }
137
138 @NonNull
139 private static String getCorrespondingFakeName(@NonNull String name) {
140 if ("<init>".equals(name)) {
141 return "$init";
142 }
143
144 if ("<clinit>".equals(name)) {
145 return "$clinit";
146 }
147
148 return name;
149 }
150
151 private boolean isFakedSuperclass() {
152 return fakedClass != fakeMethods.getRealClass();
153 }
154
155 private void generateCodeForInterceptedNativeMethod() {
156 generateCallToUpdateFakeState();
157 generateCallToFakeMethod();
158 generateMethodReturn();
159 mw.visitMaxStack(1);
160 }
161
162 @Override
163 protected void generateInterceptionCode() {
164 Label startOfRealImplementation = null;
165
166 if (!isStatic(methodAccess) && !isConstructor && isFakedSuperclass()) {
167 Class<?> targetClass = fakeMethods.getRealClass();
168
169 if (fakedClass.getClassLoader() == targetClass.getClassLoader()) {
170 startOfRealImplementation = new Label();
171 mw.visitVarInsn(ALOAD, 0);
172 mw.visitTypeInsn(INSTANCEOF, JavaType.getInternalName(targetClass));
173 mw.visitJumpInsn(IFEQ, startOfRealImplementation);
174 }
175 }
176
177 generateCallToUpdateFakeState();
178
179 if (isConstructor) {
180 generateConditionalCallForFakedConstructor();
181 } else {
182 generateConditionalCallForFakedMethod(startOfRealImplementation);
183 }
184 }
185
186 private void generateCallToUpdateFakeState() {
187 if (useClassLoadingBridgeForUpdatingFakeState) {
188 generateCallToControlMethodThroughClassLoadingBridge();
189 mw.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
190 mw.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
191 } else {
192 mw.visitLdcInsn(fakeMethods.getFakeClassInternalName());
193 generateCodeToPassThisOrNullIfStaticMethod();
194 mw.visitIntInsn(SIPUSH, fakeMethod.getIndexForFakeState());
195 mw.visitMethodInsn(INVOKESTATIC, "mockit/internal/state/TestRun", "updateFakeState",
196 "(Ljava/lang/String;Ljava/lang/Object;I)Z", false);
197 }
198 }
199
200 private void generateCallToControlMethodThroughClassLoadingBridge() {
201 generateCodeToObtainInstanceOfClassLoadingBridge(FakeBridge.MB);
202
203
204 generateCodeToPassThisOrNullIfStaticMethod();
205 mw.visitInsn(ACONST_NULL);
206
207
208 generateCodeToCreateArrayOfObject(2);
209
210 int i = 0;
211 generateCodeToFillArrayElement(i, fakeMethods.getFakeClassInternalName());
212 i++;
213 generateCodeToFillArrayElement(i, fakeMethod.getIndexForFakeState());
214
215 generateCallToInvocationHandler();
216 }
217
218 private void generateConditionalCallForFakedMethod(@Nullable Label startOfRealImplementation) {
219 if (startOfRealImplementation == null) {
220
221 startOfRealImplementation = new Label();
222 }
223
224 mw.visitJumpInsn(IFEQ, startOfRealImplementation);
225 generateCallToFakeMethod();
226 generateMethodReturn();
227 mw.visitLabel(startOfRealImplementation);
228 }
229
230 private void generateConditionalCallForFakedConstructor() {
231 generateCallToFakeMethod();
232
233 int jumpInsnOpcode;
234
235 if (shouldUseClassLoadingBridge()) {
236 mw.visitLdcInsn(VOID_TYPE);
237 jumpInsnOpcode = IF_ACMPEQ;
238 } else {
239 jumpInsnOpcode = fakeMethod.hasInvocationParameter() ? IFNE : IFEQ;
240 }
241
242 Label startOfRealImplementation = new Label();
243 mw.visitJumpInsn(jumpInsnOpcode, startOfRealImplementation);
244 mw.visitInsn(RETURN);
245 mw.visitLabel(startOfRealImplementation);
246 }
247
248 private void generateCallToFakeMethod() {
249 if (shouldUseClassLoadingBridge()) {
250 generateCallToFakeMethodThroughClassLoadingBridge();
251 } else {
252 generateDirectCallToFakeMethod();
253 }
254 }
255
256 private boolean shouldUseClassLoadingBridge() {
257 return useClassLoadingBridge || !fakeMethod.isPublic();
258 }
259
260 private void generateCallToFakeMethodThroughClassLoadingBridge() {
261 generateCodeToObtainInstanceOfClassLoadingBridge(FakeMethodBridge.MB);
262
263
264 boolean isStatic = generateCodeToPassThisOrNullIfStaticMethod();
265 mw.visitInsn(ACONST_NULL);
266
267
268 JavaType[] argTypes = JavaType.getArgumentTypes(methodDesc);
269 generateCodeToCreateArrayOfObject(6 + argTypes.length);
270
271 int i = 0;
272 generateCodeToFillArrayElement(i, fakeMethods.getFakeClassInternalName());
273 i++;
274 generateCodeToFillArrayElement(i, classDesc);
275 i++;
276 generateCodeToFillArrayElement(i, methodAccess);
277 i++;
278
279 if (fakeMethod.hasInvocationParameterOnly()) {
280 generateCodeToFillArrayElement(i, methodName);
281 i++;
282 generateCodeToFillArrayElement(i, methodDesc);
283 } else {
284 generateCodeToFillArrayElement(i, fakeMethod.name);
285 i++;
286 generateCodeToFillArrayElement(i, fakeMethod.desc);
287 }
288 i++;
289
290 generateCodeToFillArrayElement(i, fakeMethod.getIndexForFakeState());
291 i++;
292
293 generateCodeToFillArrayWithParameterValues(argTypes, i, isStatic ? 0 : 1);
294 generateCallToInvocationHandler();
295 }
296
297 private void generateDirectCallToFakeMethod() {
298 String fakeClassDesc = fakeMethods.getFakeClassInternalName();
299 int invokeOpcode;
300
301 if (fakeMethod.isStatic()) {
302 invokeOpcode = INVOKESTATIC;
303 } else {
304 generateCodeToObtainFakeInstance(fakeClassDesc);
305 invokeOpcode = INVOKEVIRTUAL;
306 }
307
308 boolean canProceedIntoConstructor = generateArgumentsForFakeMethodInvocation();
309 mw.visitMethodInsn(invokeOpcode, fakeClassDesc, fakeMethod.name, fakeMethod.desc, false);
310
311 if (canProceedIntoConstructor) {
312 mw.visitMethodInsn(INVOKEVIRTUAL, "mockit/internal/faking/FakeInvocation", "shouldProceedIntoConstructor",
313 "()Z", false);
314 }
315 }
316
317 private void generateCodeToObtainFakeInstance(@NonNull String fakeClassDesc) {
318 mw.visitLdcInsn(fakeClassDesc);
319 generateCodeToPassThisOrNullIfStaticMethod();
320 mw.visitMethodInsn(INVOKESTATIC, "mockit/internal/state/TestRun", "getFake",
321 "(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;", false);
322 mw.visitTypeInsn(CHECKCAST, fakeClassDesc);
323 }
324
325 private boolean generateArgumentsForFakeMethodInvocation() {
326 String fakedDesc = fakeMethod.hasInvocationParameterOnly() ? methodDesc
327 : fakeMethod.fakeDescWithoutInvocationParameter;
328 JavaType[] argTypes = JavaType.getArgumentTypes(fakedDesc);
329 int varIndex = isStatic(methodAccess) ? 0 : 1;
330 boolean canProceedIntoConstructor = false;
331
332 if (fakeMethod.hasInvocationParameter()) {
333 generateCallToCreateNewFakeInvocation(argTypes, varIndex);
334
335
336 if (isConstructor) {
337 mw.visitInsn(fakeMethod.isStatic() ? DUP : DUP_X1);
338 canProceedIntoConstructor = true;
339 }
340 }
341
342 if (!fakeMethod.hasInvocationParameterOnly()) {
343 passArgumentsForFakeMethodCall(argTypes, varIndex);
344 }
345
346 return canProceedIntoConstructor;
347 }
348
349 private void generateCallToCreateNewFakeInvocation(@NonNull JavaType[] argTypes,
350 @NonNegative int initialParameterIndex) {
351 generateCodeToPassThisOrNullIfStaticMethod();
352
353 int argCount = argTypes.length;
354
355 if (argCount == 0) {
356 mw.visitInsn(ACONST_NULL);
357 } else {
358 generateCodeToCreateArrayOfObject(argCount);
359 generateCodeToFillArrayWithParameterValues(argTypes, 0, initialParameterIndex);
360 }
361
362 mw.visitLdcInsn(fakeMethods.getFakeClassInternalName());
363 mw.visitIntInsn(SIPUSH, fakeMethod.getIndexForFakeState());
364 mw.visitLdcInsn(classDesc);
365 mw.visitLdcInsn(methodName);
366 mw.visitLdcInsn(methodDesc);
367
368 mw.visitMethodInsn(INVOKESTATIC, "mockit/internal/faking/FakeInvocation", "create",
369 "(Ljava/lang/Object;[Ljava/lang/Object;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)"
370 + "Lmockit/internal/faking/FakeInvocation;",
371 false);
372 }
373
374 private void passArgumentsForFakeMethodCall(@NonNull JavaType[] argTypes, @NonNegative int varIndex) {
375 boolean forGenericMethod = fakeMethod.isForGenericMethod();
376
377 for (JavaType argType : argTypes) {
378 int opcode = argType.getOpcode(ILOAD);
379 mw.visitVarInsn(opcode, varIndex);
380
381 if (forGenericMethod && argType instanceof ReferenceType) {
382 String typeDesc = ((ReferenceType) argType).getInternalName();
383 mw.visitTypeInsn(CHECKCAST, typeDesc);
384 }
385
386 varIndex += argType.getSize();
387 }
388 }
389
390 private void generateMethodReturn() {
391 if (shouldUseClassLoadingBridge() || fakeMethod.isAdvice) {
392 generateReturnWithObjectAtTopOfTheStack(methodDesc);
393 } else {
394 JavaType returnType = JavaType.getReturnType(methodDesc);
395 mw.visitInsn(returnType.getOpcode(IRETURN));
396 }
397 }
398 }