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