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