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