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;
7   
8   import static java.lang.reflect.Modifier.isNative;
9   import static java.lang.reflect.Modifier.isStatic;
10  
11  import static mockit.asm.jvmConstants.Opcodes.AASTORE;
12  import static mockit.asm.jvmConstants.Opcodes.ACONST_NULL;
13  import static mockit.asm.jvmConstants.Opcodes.ALOAD;
14  import static mockit.asm.jvmConstants.Opcodes.ANEWARRAY;
15  import static mockit.asm.jvmConstants.Opcodes.DUP;
16  import static mockit.asm.jvmConstants.Opcodes.GETSTATIC;
17  import static mockit.asm.jvmConstants.Opcodes.ICONST_0;
18  import static mockit.asm.jvmConstants.Opcodes.ILOAD;
19  import static mockit.asm.jvmConstants.Opcodes.INVOKEINTERFACE;
20  import static mockit.asm.jvmConstants.Opcodes.INVOKESPECIAL;
21  import static mockit.asm.jvmConstants.Opcodes.INVOKESTATIC;
22  import static mockit.asm.jvmConstants.Opcodes.IRETURN;
23  import static mockit.asm.jvmConstants.Opcodes.NEW;
24  import static mockit.asm.jvmConstants.Opcodes.NEWARRAY;
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.asm.annotations.AnnotationVisitor;
32  import mockit.asm.classes.ClassInfo;
33  import mockit.asm.classes.ClassReader;
34  import mockit.asm.classes.ClassWriter;
35  import mockit.asm.classes.WrappingClassVisitor;
36  import mockit.asm.controlFlow.Label;
37  import mockit.asm.jvmConstants.Access;
38  import mockit.asm.jvmConstants.ClassVersion;
39  import mockit.asm.methods.MethodVisitor;
40  import mockit.asm.methods.MethodWriter;
41  import mockit.asm.methods.WrappingMethodVisitor;
42  import mockit.asm.types.ArrayType;
43  import mockit.asm.types.JavaType;
44  import mockit.asm.types.ObjectType;
45  import mockit.asm.types.PrimitiveType;
46  import mockit.asm.types.ReferenceType;
47  import mockit.internal.expectations.ExecutionMode;
48  import mockit.internal.state.TestRun;
49  import mockit.internal.util.ClassLoad;
50  import mockit.internal.util.TypeConversionBytecode;
51  
52  import org.checkerframework.checker.index.qual.NonNegative;
53  
54  public class BaseClassModifier extends WrappingClassVisitor {
55      private static final int METHOD_ACCESS_MASK = 0xFFFF - Access.ABSTRACT - Access.NATIVE;
56      protected static final JavaType VOID_TYPE = ObjectType.create("java/lang/Void");
57  
58      @NonNull
59      protected final MethodVisitor methodAnnotationsVisitor = new MethodVisitor() {
60          @Override
61          public AnnotationVisitor visitAnnotation(@NonNull String desc) {
62              return mw.visitAnnotation(desc);
63          }
64      };
65  
66      protected MethodWriter mw;
67      protected boolean useClassLoadingBridge;
68      protected String superClassName;
69      protected String classDesc;
70      protected int methodAccess;
71      protected String methodName;
72      protected String methodDesc;
73  
74      protected BaseClassModifier(@NonNull ClassReader classReader) {
75          super(new ClassWriter(classReader));
76      }
77  
78      protected final void setUseClassLoadingBridge(@Nullable ClassLoader classLoader) {
79          useClassLoadingBridge = ClassLoad.isClassLoaderWithNoDirectAccess(classLoader);
80      }
81  
82      @Override
83      public void visit(int version, int access, @NonNull String name, @NonNull ClassInfo additionalInfo) {
84          int modifiedVersion = version;
85          int originalVersion = version & 0xFFFF;
86  
87          if (originalVersion < ClassVersion.V5) {
88              // LDC instructions (see MethodVisitor#visitLdcInsn) are more capable in JVMs with support for class files
89              // of
90              // version 49 (Java 5) or newer, so we "upgrade" it to avoid a VerifyError:
91              modifiedVersion = ClassVersion.V5;
92          }
93  
94          cw.visit(modifiedVersion, access, name, additionalInfo);
95          superClassName = additionalInfo.superName;
96          classDesc = name;
97      }
98  
99      /**
100      * Just creates a new MethodWriter which will write out the method bytecode when visited.
101      * <p>
102      * Removes any "abstract" or "native" modifiers for the modified version.
103      */
104     protected final void startModifiedMethodVersion(int access, @NonNull String name, @NonNull String desc,
105             @Nullable String signature, @Nullable String[] exceptions) {
106         mw = cw.visitMethod(access & METHOD_ACCESS_MASK, name, desc, signature, exceptions);
107         methodAccess = access;
108         methodName = name;
109         methodDesc = desc;
110 
111         if (isNative(access)) {
112             TestRun.mockFixture().addRedefinedClassWithNativeMethods(classDesc);
113         }
114     }
115 
116     public final boolean wasModified() {
117         return methodName != null;
118     }
119 
120     protected final void generateDirectCallToHandler(@NonNull String className, int access, @NonNull String name,
121             @NonNull String desc, @Nullable String genericSignature) {
122         generateDirectCallToHandler(className, access, name, desc, genericSignature, ExecutionMode.Regular);
123     }
124 
125     protected final void generateDirectCallToHandler(@NonNull String className, int access, @NonNull String name,
126             @NonNull String desc, @Nullable String genericSignature, @NonNull ExecutionMode executionMode) {
127         // First argument: the mock instance, if any.
128         boolean isStatic = generateCodeToPassThisOrNullIfStaticMethod(access);
129 
130         // Second argument: method access flags.
131         mw.visitLdcInsn(access);
132 
133         // Third argument: class name.
134         mw.visitLdcInsn(className);
135 
136         // Fourth argument: method signature.
137         mw.visitLdcInsn(name + desc);
138 
139         // Fifth argument: generic signature, or null if none.
140         generateInstructionToLoadNullableString(genericSignature);
141 
142         // Sixth argument: indicate regular or special modes of execution.
143         mw.visitLdcInsn(executionMode.ordinal());
144 
145         // Seventh argument: array with invocation arguments.
146         JavaType[] argTypes = JavaType.getArgumentTypes(desc);
147         int argCount = argTypes.length;
148 
149         if (argCount == 0) {
150             mw.visitInsn(ACONST_NULL);
151         } else {
152             generateCodeToCreateArrayOfObject(argCount);
153             generateCodeToFillArrayWithParameterValues(argTypes, 0, isStatic ? 0 : 1);
154         }
155 
156         mw.visitMethodInsn(INVOKESTATIC, "mockit/internal/expectations/RecordAndReplayExecution", "recordOrReplay",
157                 "(Ljava/lang/Object;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;I[Ljava/lang/Object;)Ljava/lang/Object;",
158                 false);
159     }
160 
161     private void generateInstructionToLoadNullableString(@Nullable String text) {
162         if (text == null) {
163             mw.visitInsn(ACONST_NULL);
164         } else {
165             mw.visitLdcInsn(text);
166         }
167     }
168 
169     protected final void generateReturnWithObjectAtTopOfTheStack(@NonNull String mockedMethodDesc) {
170         JavaType returnType = JavaType.getReturnType(mockedMethodDesc);
171         TypeConversionBytecode.generateCastFromObject(mw, returnType);
172         mw.visitInsn(returnType.getOpcode(IRETURN));
173     }
174 
175     protected final boolean generateCodeToPassThisOrNullIfStaticMethod() {
176         return generateCodeToPassThisOrNullIfStaticMethod(methodAccess);
177     }
178 
179     private boolean generateCodeToPassThisOrNullIfStaticMethod(int access) {
180         boolean isStatic = isStatic(access);
181 
182         if (isStatic) {
183             mw.visitInsn(ACONST_NULL);
184         } else {
185             mw.visitVarInsn(ALOAD, 0);
186         }
187 
188         return isStatic;
189     }
190 
191     protected final void generateCodeToCreateArrayOfObject(@NonNegative int arrayLength) {
192         mw.visitIntInsn(SIPUSH, arrayLength);
193         mw.visitTypeInsn(ANEWARRAY, "java/lang/Object");
194     }
195 
196     protected final void generateCodeToFillArrayWithParameterValues(@NonNull JavaType[] parameterTypes,
197             @NonNegative int initialArrayIndex, @NonNegative int initialParameterIndex) {
198         int i = initialArrayIndex;
199         int j = initialParameterIndex;
200 
201         for (JavaType parameterType : parameterTypes) {
202             mw.visitInsn(DUP);
203             mw.visitIntInsn(SIPUSH, i);
204             i++;
205             mw.visitVarInsn(parameterType.getOpcode(ILOAD), j);
206             TypeConversionBytecode.generateCastToObject(mw, parameterType);
207             mw.visitInsn(AASTORE);
208             j += parameterType.getSize();
209         }
210     }
211 
212     protected final void generateCodeToObtainInstanceOfClassLoadingBridge(
213             @NonNull ClassLoadingBridge classLoadingBridge) {
214         String hostClassName = ClassLoadingBridge.getHostClassName();
215         mw.visitFieldInsn(GETSTATIC, hostClassName, classLoadingBridge.id, "Ljava/lang/reflect/InvocationHandler;");
216     }
217 
218     protected final void generateCodeToFillArrayElement(@NonNegative int arrayIndex, @Nullable Object value) {
219         mw.visitInsn(DUP);
220         mw.visitIntInsn(SIPUSH, arrayIndex);
221 
222         if (value == null) {
223             mw.visitInsn(ACONST_NULL);
224         } else if (value instanceof Integer) {
225             mw.visitIntInsn(SIPUSH, (Integer) value);
226             mw.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
227         } else {
228             mw.visitLdcInsn(value);
229         }
230 
231         mw.visitInsn(AASTORE);
232     }
233 
234     private void pushDefaultValueForType(@NonNull JavaType type) {
235         if (type instanceof ArrayType) {
236             generateCreationOfEmptyArray((ArrayType) type);
237         } else {
238             int constOpcode = type.getConstOpcode();
239 
240             if (constOpcode > 0) {
241                 mw.visitInsn(constOpcode);
242             }
243         }
244     }
245 
246     private void generateCreationOfEmptyArray(@NonNull ArrayType arrayType) {
247         int dimensions = arrayType.getDimensions();
248 
249         for (int dimension = 0; dimension < dimensions; dimension++) {
250             mw.visitInsn(ICONST_0);
251         }
252 
253         if (dimensions > 1) {
254             mw.visitMultiANewArrayInsn(arrayType.getDescriptor(), dimensions);
255             return;
256         }
257 
258         JavaType elementType = arrayType.getElementType();
259 
260         if (elementType instanceof ReferenceType) {
261             mw.visitTypeInsn(ANEWARRAY, ((ReferenceType) elementType).getInternalName());
262         } else {
263             int typeCode = PrimitiveType.getArrayElementType((PrimitiveType) elementType);
264             mw.visitIntInsn(NEWARRAY, typeCode);
265         }
266     }
267 
268     protected final void generateCallToInvocationHandler() {
269         mw.visitMethodInsn(INVOKEINTERFACE, "java/lang/reflect/InvocationHandler", "invoke",
270                 "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", true);
271     }
272 
273     protected final void generateEmptyImplementation(@NonNull String desc) {
274         JavaType returnType = JavaType.getReturnType(desc);
275         pushDefaultValueForType(returnType);
276         mw.visitInsn(returnType.getOpcode(IRETURN));
277         mw.visitMaxStack(1);
278     }
279 
280     protected final void generateEmptyImplementation() {
281         mw.visitInsn(RETURN);
282         mw.visitMaxStack(1);
283     }
284 
285     @NonNull
286     protected final MethodVisitor copyOriginalImplementationWithInjectedInterceptionCode() {
287         if ("<init>".equals(methodName)) {
288             return new DynamicConstructorModifier();
289         }
290 
291         generateInterceptionCode();
292         return new DynamicModifier();
293     }
294 
295     protected void generateInterceptionCode() {
296     }
297 
298     private class DynamicModifier extends WrappingMethodVisitor {
299         DynamicModifier() {
300             super(BaseClassModifier.this.mw);
301         }
302 
303         @Override
304         public final void visitLocalVariable(@NonNull String name, @NonNull String desc, @Nullable String signature,
305                 @NonNull Label start, @NonNull Label end, @NonNegative int index) {
306             // For some reason, the start position for "this" gets displaced by bytecode inserted at the beginning,
307             // in a method modified by the EMMA tool. If not treated, this causes a ClassFormatError.
308             if (end.position > 0 && start.position > end.position) {
309                 start.position = end.position;
310             }
311 
312             // Ignores any local variable with required information missing, to avoid a VerifyError/ClassFormatError.
313             if (start.position > 0 && end.position > 0) {
314                 mw.visitLocalVariable(name, desc, signature, start, end, index);
315             }
316         }
317     }
318 
319     private final class DynamicConstructorModifier extends DynamicModifier {
320         private boolean pendingCallToConstructorOfSameClass;
321         private boolean callToAnotherConstructorAlreadyCopied;
322 
323         @Override
324         public void visitTypeInsn(int opcode, @NonNull String typeDesc) {
325             mw.visitTypeInsn(opcode, typeDesc);
326 
327             if (opcode == NEW && !callToAnotherConstructorAlreadyCopied && typeDesc.equals(classDesc)) {
328                 pendingCallToConstructorOfSameClass = true;
329             }
330         }
331 
332         @Override
333         public void visitMethodInsn(int opcode, @NonNull String owner, @NonNull String name, @NonNull String desc,
334                 boolean itf) {
335             mw.visitMethodInsn(opcode, owner, name, desc, itf);
336 
337             if (pendingCallToConstructorOfSameClass) {
338                 if (opcode == INVOKESPECIAL && "<init>".equals(name) && owner.equals(classDesc)) {
339                     pendingCallToConstructorOfSameClass = false;
340                 }
341             } else if (opcode == INVOKESPECIAL && !callToAnotherConstructorAlreadyCopied && "<init>".equals(name)
342                     && (owner.equals(superClassName) || owner.equals(classDesc))) {
343                 generateInterceptionCode();
344                 callToAnotherConstructorAlreadyCopied = true;
345             }
346         }
347     }
348 }