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.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   * Responsible for generating all necessary bytecode in the redefined (real) class. Such code will redirect calls made
47   * on "real" methods to equivalent calls on the corresponding "fake" methods. The original code won't be executed by the
48   * running JVM until the class redefinition is undone.
49   * <p>
50   * Methods in the real class with no corresponding fake methods are unaffected.
51   * <p>
52   * Any fields (static or not) in the real class remain untouched.
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       * Initializes the modifier for a given real/fake class pair.
67       * <p>
68       * The fake instance provided will receive calls for any instance methods defined in the fake class. Therefore, it
69       * needs to be later recovered by the modified bytecode inside the real method. To enable this, the fake instance is
70       * added to a global data structure made available through the {@link TestRun#getFake(String, Object)} method.
71       *
72       * @param cr
73       *            the class file reader for the real class
74       * @param realClass
75       *            the class to be faked, or a base type of an implementation class to be faked
76       * @param fake
77       *            an instance of the fake class
78       * @param fakeMethods
79       *            contains the set of fake methods collected from the fake class; each fake method is identified by a
80       *            pair composed of "name" and "desc", where "name" is the method name, and "desc" is the JVM internal
81       *            description of the parameters; once the real class modification is complete this set will be empty,
82       *            unless no corresponding real method was found for any of its method identifiers
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                 // Marks a matching fake method (if any) as having the corresponding faked method.
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); // dummy value, real one is calculated by ASM
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         // First and second "invoke" arguments:
203         generateCodeToPassThisOrNullIfStaticMethod();
204         mw.visitInsn(ACONST_NULL);
205 
206         // Create array for call arguments (third "invoke" argument):
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             // noinspection AssignmentToMethodParameter
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         // First and second "invoke" arguments:
263         boolean isStatic = generateCodeToPassThisOrNullIfStaticMethod();
264         mw.visitInsn(ACONST_NULL);
265 
266         // Create array for call arguments (third "invoke" argument):
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             // When invoking a constructor, the invocation object will need to be consulted for proceeding:
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 }