View Javadoc
1   /*
2    * MIT License
3    * Copyright (c) 2006-2025 JMockit developers
4    * See LICENSE file for full license text.
5    */
6   package mockit.internal.classGeneration;
7   
8   import static java.util.Arrays.asList;
9   
10  import static mockit.asm.jvmConstants.Opcodes.ALOAD;
11  import static mockit.asm.jvmConstants.Opcodes.INVOKESPECIAL;
12  
13  import edu.umd.cs.findbugs.annotations.NonNull;
14  import edu.umd.cs.findbugs.annotations.Nullable;
15  
16  import java.lang.reflect.Method;
17  import java.lang.reflect.Type;
18  import java.util.ArrayList;
19  import java.util.HashSet;
20  import java.util.List;
21  import java.util.Set;
22  
23  import mockit.asm.classes.ClassInfo;
24  import mockit.asm.classes.ClassReader;
25  import mockit.asm.fields.FieldVisitor;
26  import mockit.asm.jvmConstants.Access;
27  import mockit.asm.metadata.ClassMetadataReader;
28  import mockit.asm.metadata.ClassMetadataReader.MethodInfo;
29  import mockit.asm.methods.MethodVisitor;
30  import mockit.asm.types.JavaType;
31  import mockit.internal.BaseClassModifier;
32  import mockit.internal.ClassFile;
33  import mockit.internal.util.TypeDescriptor;
34  
35  public class BaseSubclassGenerator extends BaseClassModifier {
36      private static final int CLASS_ACCESS_MASK = 0xFFFF - Access.ABSTRACT;
37      private static final int CONSTRUCTOR_ACCESS_MASK = Access.PUBLIC + Access.PROTECTED;
38  
39      // Fixed initial state:
40      @NonNull
41      Class<?> baseClass;
42      @NonNull
43      private final String subclassName;
44      @Nullable
45      protected final MockedTypeInfo mockedTypeInfo;
46      private final boolean copyConstructors;
47  
48      // Helper fields for mutable state:
49      @NonNull
50      private final List<String> implementedMethods;
51      @Nullable
52      private String superClassOfSuperClass;
53      private Set<String> superInterfaces;
54  
55      protected BaseSubclassGenerator(@NonNull Class<?> baseClass, @NonNull ClassReader cr,
56              @Nullable Type genericMockedType, @NonNull String subclassName, boolean copyConstructors) {
57          super(cr);
58          this.baseClass = baseClass;
59          this.subclassName = subclassName.replace('.', '/');
60          mockedTypeInfo = genericMockedType == null ? null : new MockedTypeInfo(genericMockedType);
61          this.copyConstructors = copyConstructors;
62          implementedMethods = new ArrayList<>();
63      }
64  
65      @Override
66      public void visit(int version, int access, @NonNull String name, @NonNull ClassInfo additionalInfo) {
67          ClassInfo subClassInfo = new ClassInfo();
68          subClassInfo.superName = name;
69          subClassInfo.signature = mockedTypeInfo == null ? additionalInfo.signature
70                  : mockedTypeInfo.implementationSignature;
71          int subclassAccess = access & CLASS_ACCESS_MASK | Access.FINAL;
72  
73          super.visit(version, subclassAccess, subclassName, subClassInfo);
74  
75          superClassOfSuperClass = additionalInfo.superName;
76          superInterfaces = new HashSet<>();
77  
78          String[] interfaces = additionalInfo.interfaces;
79  
80          if (interfaces.length > 0) {
81              superInterfaces.addAll(asList(interfaces));
82          }
83      }
84  
85      @Override
86      public final void visitInnerClass(@NonNull String name, @Nullable String outerName, @Nullable String innerName,
87              int access) {
88      }
89  
90      @Override
91      @Nullable
92      public final FieldVisitor visitField(int access, @NonNull String name, @NonNull String desc,
93              @Nullable String signature, @Nullable Object value) {
94          return null;
95      }
96  
97      @Override
98      @Nullable
99      public MethodVisitor visitMethod(int access, @NonNull String name, @NonNull String desc, @Nullable String signature,
100             @Nullable String[] exceptions) {
101         if (copyConstructors && "<init>".equals(name)) {
102             if ((access & CONSTRUCTOR_ACCESS_MASK) != 0) {
103                 generateConstructorDelegatingToSuper(desc, signature, exceptions);
104             }
105         } else {
106             // Inherits from super-class when non-abstract; otherwise, creates implementation for abstract method.
107             generateImplementationIfAbstractMethod(superClassName, access, name, desc, signature, exceptions);
108         }
109 
110         return null;
111     }
112 
113     private void generateConstructorDelegatingToSuper(@NonNull String desc, @Nullable String signature,
114             @Nullable String[] exceptions) {
115         mw = cw.visitMethod(Access.PUBLIC, "<init>", desc, signature, exceptions);
116         mw.visitVarInsn(ALOAD, 0);
117         int varIndex = 1;
118 
119         for (JavaType paramType : JavaType.getArgumentTypes(desc)) {
120             int loadOpcode = paramType.getLoadOpcode();
121             mw.visitVarInsn(loadOpcode, varIndex);
122             varIndex++;
123         }
124 
125         mw.visitMethodInsn(INVOKESPECIAL, superClassName, "<init>", desc, false);
126         generateEmptyImplementation();
127     }
128 
129     private void generateImplementationIfAbstractMethod(String className, int access, @NonNull String name,
130             @NonNull String desc, @Nullable String signature, @Nullable String[] exceptions) {
131         if (!"<init>".equals(name)) {
132             String methodNameAndDesc = name + desc;
133 
134             if (!implementedMethods.contains(methodNameAndDesc)) {
135                 if ((access & Access.ABSTRACT) != 0) {
136                     generateMethodImplementation(className, access, name, desc, signature, exceptions);
137                 }
138 
139                 implementedMethods.add(methodNameAndDesc);
140             }
141         }
142     }
143 
144     protected void generateMethodImplementation(String className, int access, @NonNull String name,
145             @NonNull String desc, @Nullable String signature, @Nullable String[] exceptions) {
146     }
147 
148     @Override
149     public void visitEnd() {
150         generateImplementationsForInheritedAbstractMethods(superClassOfSuperClass);
151 
152         while (!superInterfaces.isEmpty()) {
153             String superInterface = superInterfaces.iterator().next();
154             generateImplementationsForAbstractMethods(superInterface, false);
155             superInterfaces.remove(superInterface);
156         }
157     }
158 
159     private void generateImplementationsForInheritedAbstractMethods(@Nullable String superName) {
160         if (superName != null) {
161             generateImplementationsForAbstractMethods(superName, true);
162         }
163     }
164 
165     private void generateImplementationsForAbstractMethods(@NonNull String typeName, boolean abstractClass) {
166         if (!"java/lang/Object".equals(typeName)) {
167             byte[] typeBytecode = ClassFile.getClassFile(typeName);
168             ClassMetadataReader cmr = new ClassMetadataReader(typeBytecode);
169             String[] interfaces = cmr.getInterfaces();
170 
171             if (interfaces != null) {
172                 superInterfaces.addAll(asList(interfaces));
173             }
174 
175             for (MethodInfo method : cmr.getMethods()) {
176                 if (abstractClass) {
177                     generateImplementationIfAbstractMethod(typeName, method.accessFlags, method.name, method.desc, null,
178                             null);
179                 } else if (method.isAbstract()) {
180                     generateImplementationForInterfaceMethodIfMissing(typeName, method);
181                 }
182             }
183 
184             if (abstractClass) {
185                 String superClass = cmr.getSuperClass();
186                 generateImplementationsForInheritedAbstractMethods(superClass);
187             }
188         }
189     }
190 
191     private void generateImplementationForInterfaceMethodIfMissing(@NonNull String typeName,
192             @NonNull MethodInfo method) {
193         String name = method.name;
194         String desc = method.desc;
195         String methodNameAndDesc = name + desc;
196 
197         if (!implementedMethods.contains(methodNameAndDesc)) {
198             if (!hasMethodImplementation(name, desc)) {
199                 generateMethodImplementation(typeName, method.accessFlags, name, desc, null, null);
200             }
201 
202             implementedMethods.add(methodNameAndDesc);
203         }
204     }
205 
206     private boolean hasMethodImplementation(@NonNull String name, @NonNull String desc) {
207         Class<?>[] paramTypes = TypeDescriptor.getParameterTypes(desc);
208 
209         try {
210             Method method = baseClass.getMethod(name, paramTypes);
211             return !method.getDeclaringClass().isInterface();
212         } catch (NoSuchMethodException ignore) {
213             return false;
214         }
215     }
216 }