1
2
3
4
5 package mockit.internal.expectations.mocking;
6
7 import static java.lang.reflect.Modifier.ABSTRACT;
8 import static java.lang.reflect.Modifier.FINAL;
9 import static java.lang.reflect.Modifier.PRIVATE;
10 import static java.lang.reflect.Modifier.PROTECTED;
11 import static java.lang.reflect.Modifier.PUBLIC;
12 import static java.lang.reflect.Modifier.STATIC;
13 import static java.lang.reflect.Modifier.isNative;
14
15 import static mockit.asm.jvmConstants.Access.ENUM;
16 import static mockit.asm.jvmConstants.Access.SYNTHETIC;
17 import static mockit.asm.jvmConstants.Opcodes.ACONST_NULL;
18 import static mockit.asm.jvmConstants.Opcodes.DUP;
19 import static mockit.asm.jvmConstants.Opcodes.IF_ACMPEQ;
20 import static mockit.asm.jvmConstants.Opcodes.POP;
21 import static mockit.internal.expectations.MockingFilters.validateAsMockable;
22 import static mockit.internal.util.ObjectMethods.isMethodFromObject;
23 import static mockit.internal.util.Utilities.HOTSPOT_VM;
24
25 import edu.umd.cs.findbugs.annotations.NonNull;
26 import edu.umd.cs.findbugs.annotations.Nullable;
27
28 import java.util.ArrayList;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32
33 import mockit.asm.classes.ClassInfo;
34 import mockit.asm.classes.ClassReader;
35 import mockit.asm.controlFlow.Label;
36 import mockit.asm.methods.MethodVisitor;
37 import mockit.asm.types.JavaType;
38 import mockit.internal.BaseClassModifier;
39 import mockit.internal.expectations.ExecutionMode;
40
41 final class MockedClassModifier extends BaseClassModifier {
42 private static final int METHOD_ACCESS_MASK = PRIVATE + SYNTHETIC + ABSTRACT;
43 private static final int PUBLIC_OR_PROTECTED = PUBLIC + PROTECTED;
44 private static final boolean NATIVE_UNSUPPORTED = !HOTSPOT_VM;
45
46 private static final Map<String, String> FILTERS = new HashMap<>(4);
47 static {
48 FILTERS.put("java/lang/Object", "<init> clone getClass hashCode wait notify notifyAll ");
49 FILTERS.put("java/io/File", "compareTo equals getName getPath hashCode toString ");
50 FILTERS.put("java/util/logging/Logger", "<init> getName ");
51 FILTERS.put("java/util/jar/JarEntry", "<init> ");
52 }
53
54 @Nullable
55 private final MockedType mockedType;
56 private String className;
57 private String methodSignature;
58 @Nullable
59 private String baseClassNameForCapturedInstanceMethods;
60 @NonNull
61 private ExecutionMode executionMode;
62 private boolean isProxy;
63 @Nullable
64 private String defaultFilters;
65 @Nullable
66 List<String> enumSubclasses;
67
68 MockedClassModifier(@Nullable ClassLoader classLoader, @NonNull ClassReader classReader,
69 @Nullable MockedType typeMetadata) {
70 super(classReader);
71 mockedType = typeMetadata;
72 setUseClassLoadingBridge(classLoader);
73 executionMode = typeMetadata != null && typeMetadata.injectable ? ExecutionMode.PerInstance
74 : ExecutionMode.Regular;
75 }
76
77 void useDynamicMocking() {
78 executionMode = ExecutionMode.Partial;
79 }
80
81 void setClassNameForCapturedInstanceMethods(@NonNull String internalClassName) {
82 baseClassNameForCapturedInstanceMethods = internalClassName;
83 }
84
85 @Override
86 public void visit(int version, int access, @NonNull String name, @NonNull ClassInfo additionalInfo) {
87 validateMockingOfJREClass(name);
88
89 super.visit(version, access, name, additionalInfo);
90 isProxy = "java/lang/reflect/Proxy".equals(additionalInfo.superName);
91
92 if (isProxy) {
93 className = additionalInfo.interfaces[0];
94 } else {
95 className = name;
96 defaultFilters = FILTERS.get(name);
97 }
98
99 if (baseClassNameForCapturedInstanceMethods != null) {
100 className = baseClassNameForCapturedInstanceMethods;
101 }
102 }
103
104 private void validateMockingOfJREClass(@NonNull String internalName) {
105 if (internalName.startsWith("java/")) {
106 validateAsMockable(internalName);
107
108 if (executionMode == ExecutionMode.Regular && mockedType != null && isFullMockingDisallowed(internalName)) {
109 String modifyingClassName = internalName.replace('/', '.');
110
111 if (modifyingClassName.equals(mockedType.getClassType().getName())) {
112 throw new IllegalArgumentException("Class " + modifyingClassName
113 + " cannot be @Mocked fully; instead, use @Injectable or partial mocking");
114 }
115 }
116 }
117 }
118
119 private static boolean isFullMockingDisallowed(@NonNull String classDesc) {
120 return classDesc.startsWith("java/io/") && ("java/io/FileOutputStream".equals(classDesc)
121 || "java/io/FileInputStream".equals(classDesc) || "java/io/FileWriter".equals(classDesc)
122 || "java/io/PrintWriter java/io/Writer java/io/DataInputStream".contains(classDesc));
123 }
124
125 @Override
126 public void visitInnerClass(@NonNull String name, @Nullable String outerName, @Nullable String innerName,
127 int access) {
128 cw.visitInnerClass(name, outerName, innerName, access);
129
130
131
132 if (access == ENUM + FINAL || access == ENUM + STATIC) {
133 if (enumSubclasses == null) {
134 enumSubclasses = new ArrayList<>();
135 }
136
137 enumSubclasses.add(name);
138 }
139 }
140
141 @Nullable
142 @Override
143 public MethodVisitor visitMethod(final int access, @NonNull final String name, @NonNull final String desc,
144 @Nullable final String signature, @Nullable String[] exceptions) {
145 if ((access & METHOD_ACCESS_MASK) != 0) {
146 return unmodifiedBytecode(access, name, desc, signature, exceptions);
147 }
148
149 methodSignature = signature;
150
151 if ("<init>".equals(name)) {
152 if (isConstructorNotAllowedByMockingFilters(name)) {
153 return unmodifiedBytecode(access, name, desc, signature, exceptions);
154 }
155 } else {
156 if (stubOutFinalizeMethod(access, name, desc)) {
157 return null;
158 }
159
160 if ("<clinit>".equals(name)) {
161 return stubOutClassInitializationIfApplicable(access);
162 }
163
164 if (isMethodNotToBeMocked(access, name, desc) || isMethodNotAllowedByMockingFilters(access, name)) {
165 return unmodifiedBytecode(access, name, desc, signature, exceptions);
166 }
167 }
168
169 startModifiedMethodVersion(access, name, desc, signature, exceptions);
170
171 if (isNative(methodAccess)) {
172 generateEmptyImplementation(methodDesc);
173 return methodAnnotationsVisitor;
174 }
175
176 return copyOriginalImplementationWithInjectedInterceptionCode();
177 }
178
179 @NonNull
180 private MethodVisitor unmodifiedBytecode(int access, @NonNull String name, @NonNull String desc,
181 @Nullable String signature, @Nullable String[] exceptions) {
182 return cw.visitMethod(access, name, desc, signature, exceptions);
183 }
184
185 private boolean isConstructorNotAllowedByMockingFilters(@NonNull String name) {
186 return isProxy || executionMode != ExecutionMode.Regular || isUnmockableInvocation(name);
187 }
188
189 private boolean isUnmockableInvocation(@NonNull String name) {
190 if (defaultFilters == null) {
191 return false;
192 }
193
194 int i = defaultFilters.indexOf(name);
195 return i > -1 && defaultFilters.charAt(i + name.length()) == ' ';
196 }
197
198 private boolean isMethodNotToBeMocked(int access, @NonNull String name, @NonNull String desc) {
199 return isNative(access) && (NATIVE_UNSUPPORTED || (access & PUBLIC_OR_PROTECTED) == 0)
200 || (isProxy || executionMode == ExecutionMode.Partial) && (isMethodFromObject(name, desc)
201 || "annotationType".equals(name) && "()Ljava/lang/Class;".equals(desc));
202 }
203
204 @Nullable
205 private MethodVisitor stubOutClassInitializationIfApplicable(int access) {
206 startModifiedMethodVersion(access, "<clinit>", "()V", null, null);
207
208 if (mockedType != null && mockedType.isClassInitializationToBeStubbedOut()) {
209 generateEmptyImplementation();
210 return null;
211 }
212
213 return mw;
214 }
215
216 private boolean stubOutFinalizeMethod(int access, @NonNull String name, @NonNull String desc) {
217 if ("finalize".equals(name) && "()V".equals(desc)) {
218 startModifiedMethodVersion(access, name, desc, null, null);
219 generateEmptyImplementation();
220 return true;
221 }
222
223 return false;
224 }
225
226 private boolean isMethodNotAllowedByMockingFilters(int access, @NonNull String name) {
227 return baseClassNameForCapturedInstanceMethods != null && (access & STATIC) != 0
228 || executionMode.isMethodToBeIgnored(access) || isUnmockableInvocation(name);
229 }
230
231 @Override
232 protected void generateInterceptionCode() {
233 if (useClassLoadingBridge) {
234 generateCallToHandlerThroughMockingBridge();
235 } else {
236 generateDirectCallToHandler(className, methodAccess, methodName, methodDesc, methodSignature,
237 executionMode);
238 }
239
240 generateDecisionBetweenReturningOrContinuingToRealImplementation();
241 }
242
243 private void generateCallToHandlerThroughMockingBridge() {
244 generateCodeToObtainInstanceOfClassLoadingBridge(MockedBridge.MB);
245
246
247 boolean isStatic = generateCodeToPassThisOrNullIfStaticMethod();
248 mw.visitInsn(ACONST_NULL);
249
250
251 JavaType[] argTypes = JavaType.getArgumentTypes(methodDesc);
252 generateCodeToCreateArrayOfObject(6 + argTypes.length);
253
254 int i = 0;
255 generateCodeToFillArrayElement(i, methodAccess);
256 i++;
257 generateCodeToFillArrayElement(i, className);
258 i++;
259 generateCodeToFillArrayElement(i, methodName);
260 i++;
261 generateCodeToFillArrayElement(i, methodDesc);
262 i++;
263 generateCodeToFillArrayElement(i, methodSignature);
264 i++;
265 generateCodeToFillArrayElement(i, executionMode.ordinal());
266 i++;
267
268 generateCodeToFillArrayWithParameterValues(argTypes, i, isStatic ? 0 : 1);
269 generateCallToInvocationHandler();
270 }
271
272 private void generateDecisionBetweenReturningOrContinuingToRealImplementation() {
273 Label startOfRealImplementation = new Label();
274 mw.visitInsn(DUP);
275 mw.visitLdcInsn(VOID_TYPE);
276 mw.visitJumpInsn(IF_ACMPEQ, startOfRealImplementation);
277 generateReturnWithObjectAtTopOfTheStack(methodDesc);
278 mw.visitLabel(startOfRealImplementation);
279 mw.visitInsn(POP);
280 }
281 }