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