1
2
3
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
88
89
90 modifiedVersion = ClassVersion.V5;
91 }
92
93 cw.visit(modifiedVersion, access, name, additionalInfo);
94 superClassName = additionalInfo.superName;
95 classDesc = name;
96 }
97
98
99
100
101
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
127 boolean isStatic = generateCodeToPassThisOrNullIfStaticMethod(access);
128
129
130 mw.visitLdcInsn(access);
131
132
133 mw.visitLdcInsn(className);
134
135
136 mw.visitLdcInsn(name + desc);
137
138
139 generateInstructionToLoadNullableString(genericSignature);
140
141
142 mw.visitLdcInsn(executionMode.ordinal());
143
144
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
306
307 if (end.position > 0 && start.position > end.position) {
308 start.position = end.position;
309 }
310
311
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 }