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