1 /*
2  * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 import com.sun.tools.classfile.*;
25 import com.sun.tools.classfile.Field;
26 import com.sun.tools.classfile.Method;
27 
28 import java.io.File;
29 import java.io.FilenameFilter;
30 import java.lang.reflect.*;
31 import java.util.*;
32 import java.util.function.Function;
33 import java.util.function.Predicate;
34 import java.util.function.Supplier;
35 import java.util.stream.Collectors;
36 import java.util.stream.Stream;
37 
38 /**
39  * The main class of Signature tests.
40  * Driver reads golden data of each class member that must have a Signature attribute,
41  * after that the class compares expected data with actual one.
42  *
43  * Example of usage Driver:
44  * java Driver Test
45  *
46  * Each member of the class Test should have @ExpectedSignature annotations
47  * if it must have the Signature attribute. Anonymous class cannot be annotated.
48  * So its enclosing class should be annotated and method isAnonymous
49  * of ExpectedSignature must return true.
50  */
51 public class Driver extends TestResult {
52 
53     private final static String ACC_BRIDGE = "ACC_BRIDGE";
54 
55     private final String topLevelClassName;
56     private final File[] files;
57 
Driver(String topLevelClassName)58     public Driver(String topLevelClassName) {
59         this.topLevelClassName = topLevelClassName;
60         // Get top level class and all inner classes.
61         FilenameFilter filter = (dir, file) ->
62                 file.equals(topLevelClassName + ".class")
63                         || file.matches(topLevelClassName + "\\$.*\\.class");
64         files = getClassDir().listFiles(filter);
65     }
66 
isAnonymous(String className)67     private boolean isAnonymous(String className) {
68         return className.matches(".*\\$\\d+$");
69     }
70 
getEnclosingClass(String className)71     private Class<?> getEnclosingClass(String className) throws ClassNotFoundException {
72         return Class.forName(className.replaceFirst("\\$\\d+$", ""));
73     }
74 
getExpectedClassSignature(String className, Class<?> clazz)75     private ExpectedSignature getExpectedClassSignature(String className, Class<?> clazz)
76             throws ClassNotFoundException {
77         // anonymous class cannot be annotated, so information about anonymous class
78         // is located in its enclosing class.
79         boolean isAnonymous = isAnonymous(className);
80         clazz = isAnonymous ? getEnclosingClass(className) : clazz;
81         return Stream.of(clazz.getAnnotationsByType(ExpectedSignature.class))
82                 .filter(s -> s.isAnonymous() == isAnonymous)
83                 .collect(Collectors.toMap(ExpectedSignature::descriptor, Function.identity()))
84                 .get(className);
85     }
86 
87     // Class.getName() cannot be used here, because the method can rely on signature attribute.
getClassExpectedSignature(String className, Class<?> clazz)88     private Map<String, ExpectedSignature> getClassExpectedSignature(String className, Class<?> clazz)
89             throws ClassNotFoundException {
90         Map<String, ExpectedSignature> classSignatures = new HashMap<>();
91         ExpectedSignature classSignature = getExpectedClassSignature(className, clazz);
92         if (classSignature != null) {
93             classSignatures.put(className, classSignature);
94         }
95         return classSignatures;
96     }
97 
getExpectedExecutableSignatures(Executable[] executables, Predicate<Executable> filterBridge)98     private Map<String, ExpectedSignature> getExpectedExecutableSignatures(Executable[] executables,
99                                                                            Predicate<Executable> filterBridge) {
100         return Stream.of(executables)
101                 .filter(filterBridge)
102                 .map(e -> e.getAnnotation(ExpectedSignature.class))
103                 .filter(Objects::nonNull)
104                 .collect(Collectors.toMap(ExpectedSignature::descriptor, Function.identity()));
105     }
106 
getExpectedMethodSignatures(Class<?> clazz)107     private Map<String, ExpectedSignature> getExpectedMethodSignatures(Class<?> clazz) {
108         Map<String, ExpectedSignature> methodSignatures =
109                 getExpectedExecutableSignatures(clazz.getDeclaredMethods(),
110                         m -> !((java.lang.reflect.Method) m).isBridge());
111         methodSignatures.putAll(
112                 getExpectedExecutableSignatures(clazz.getDeclaredConstructors(),
113                         m -> true));
114         return methodSignatures;
115     }
116 
getExpectedFieldSignatures(Class<?> clazz)117     private Map<String, ExpectedSignature> getExpectedFieldSignatures(Class<?> clazz) {
118         return Stream.of(clazz.getDeclaredFields())
119                 .map(f -> f.getAnnotation(ExpectedSignature.class))
120                 .filter(Objects::nonNull)
121                 .collect(Collectors.toMap(ExpectedSignature::descriptor, Function.identity()));
122     }
123 
test()124     public void test() throws TestFailedException {
125         try {
126             addTestCase("Source is " + topLevelClassName + ".java");
127             assertTrue(files.length > 0, "No class files found");
128             for (File file : files) {
129                 try {
130                     String className = file.getName().replace(".class", "");
131                     Class<?> clazz = Class.forName(className);
132                     printf("Testing class %s\n", className);
133                     ClassFile classFile = readClassFile(file);
134 
135                     // test class signature
136                     testAttribute(
137                             className,
138                             classFile,
139                             () -> (Signature_attribute) classFile.getAttribute(Attribute.Signature),
140                             getClassExpectedSignature(className, clazz).get(className));
141 
142                     testFields(getExpectedFieldSignatures(clazz), classFile);
143 
144                     testMethods(getExpectedMethodSignatures(clazz), classFile);
145                 } catch (Exception e) {
146                     addFailure(e);
147                 }
148             }
149         } catch (Exception e) {
150             addFailure(e);
151         } finally {
152             checkStatus();
153         }
154     }
155 
checkAllMembersFound(Set<String> found, Map<String, ExpectedSignature> signatures, String message)156     private void checkAllMembersFound(Set<String> found, Map<String, ExpectedSignature> signatures, String message) {
157         if (signatures != null) {
158             checkContains(found,
159                     signatures.values().stream()
160                             .map(ExpectedSignature::descriptor)
161                             .collect(Collectors.toSet()),
162                     message);
163         }
164     }
165 
testMethods(Map<String, ExpectedSignature> expectedSignatures, ClassFile classFile)166     private void testMethods(Map<String, ExpectedSignature> expectedSignatures, ClassFile classFile)
167             throws ConstantPoolException, Descriptor.InvalidDescriptor {
168         String className = classFile.getName();
169         Set<String> foundMethods = new HashSet<>();
170         for (Method method : classFile.methods) {
171             String methodName = getMethodName(classFile, method);
172             printf("Testing method %s\n", methodName);
173             if (method.access_flags.getMethodFlags().contains(ACC_BRIDGE)) {
174                 printf("Bridge method is skipped : %s\n", methodName);
175                 continue;
176             }
177             testAttribute(
178                     methodName,
179                     classFile,
180                     () -> (Signature_attribute) method.attributes.get(Attribute.Signature),
181                     expectedSignatures.get(methodName));
182             foundMethods.add(methodName);
183         }
184         checkAllMembersFound(foundMethods, expectedSignatures,
185                 "Checking that all methods of class " + className + " with Signature attribute found");
186     }
187 
getMethodName(ClassFile classFile, Method method)188     private String getMethodName(ClassFile classFile, Method method)
189             throws ConstantPoolException, Descriptor.InvalidDescriptor {
190         return String.format("%s%s",
191                 method.getName(classFile.constant_pool),
192                 method.descriptor.getParameterTypes(classFile.constant_pool));
193     }
194 
testFields(Map<String, ExpectedSignature> expectedSignatures, ClassFile classFile)195     private void testFields(Map<String, ExpectedSignature> expectedSignatures, ClassFile classFile)
196             throws ConstantPoolException {
197         String className = classFile.getName();
198         Set<String> foundFields = new HashSet<>();
199         for (Field field : classFile.fields) {
200             String fieldName = field.getName(classFile.constant_pool);
201             printf("Testing field %s\n", fieldName);
202             testAttribute(
203                     fieldName,
204                     classFile,
205                     () -> (Signature_attribute) field.attributes.get(Attribute.Signature),
206                     expectedSignatures.get(fieldName));
207             foundFields.add(fieldName);
208         }
209         checkAllMembersFound(foundFields, expectedSignatures,
210                 "Checking that all fields of class " + className + " with Signature attribute found");
211     }
212 
testAttribute( String memberName, ClassFile classFile, Supplier<Signature_attribute> sup, ExpectedSignature expectedSignature)213     private void testAttribute(
214             String memberName,
215             ClassFile classFile,
216             Supplier<Signature_attribute> sup,
217             ExpectedSignature expectedSignature)
218             throws ConstantPoolException {
219 
220         Signature_attribute attribute = sup.get();
221         if (expectedSignature != null && checkNotNull(attribute, memberName + " must have attribute")) {
222             checkEquals(classFile.constant_pool.getUTF8Value(attribute.attribute_name_index),
223                     "Signature", "Attribute's name : " + memberName);
224             checkEquals(attribute.attribute_length, 2, "Attribute's length : " + memberName);
225             checkEquals(attribute.getSignature(classFile.constant_pool),
226                     expectedSignature.signature(),
227                     "Testing signature of : " + memberName);
228         } else {
229             checkNull(attribute, memberName + " must not have attribute");
230         }
231     }
232 
main(String[] args)233     public static void main(String[] args) throws TestFailedException {
234         if (args.length != 1) {
235             throw new IllegalArgumentException("Usage: Driver <class-name>");
236         }
237         new Driver(args[0]).test();
238     }
239 }